Machine API

Machine controls a microVM through either the embedded local engine or smol cloud. This page covers the public SDK class in the current smolmachines 1.7.1 release. It is not the in-guest agent API or the standalone Cloud REST API.

TypeScript methods are asynchronous. Python’s default Machine is synchronous; use AsyncMachine when one event loop needs to drive many machines concurrently.

Create and connect

Machine.create

Creates, starts, and returns a machine.

const machine = await Machine.create(config?, connection?);
machine = Machine.create(config=None, conn=None)

Cloud creation requires MachineConfig.image and waits for readiness before returning. Set the target explicitly when the process may contain cloud credentials.

Machine.connect

Attaches to an existing machine without creating one.

const machine = await Machine.connect(id, connection?);
machine = Machine.connect(machine_id, conn=None)

For local, pass the name of a persisted machine. For cloud, pass the mach-... machine ID. connect() does not wait for readiness; call waitUntilReady() or wait_until_ready() before use.

Identity, state, and readiness

TypeScriptPythonResult
machine.namemachine.nameMachine name or identifier
await machine.state()machine.state()"created", "started", "running", or "stopped"
await machine.ready()machine.ready()Whether the machine can accept work
await machine.readyAt()machine.ready_at()RFC 3339 ready timestamp, or null
await machine.waitUntilReady(options?)machine.wait_until_ready(timeout_s=120, interval_s=1)Wait until ready or raise

State and readiness are separate. On cloud, "started" means only that the VM process launched. ready: true means the guest agent is reachable and every published port is accepting connections. create() waits for readiness; connect() does not. Check the application’s own health endpoint separately when accepting a connection is not enough to establish application health.

TypeScript readiness options:

await machine.waitUntilReady({
  timeoutMs: 120_000,
  intervalMs: 1_000,
});

Execute commands

Commands are argv arrays, not shell strings.

exec

Executes a command directly in the machine. Supported on local and cloud.

const result = await machine.exec(["sh", "-lc", "echo $MODE"], {
  env: { MODE: "test" },
  workdir: "/workspace",
  timeout: 60,
});
from smol import ExecOptions

result = machine.exec(
    ["sh", "-lc", "echo $MODE"],
    ExecOptions(
        env={"MODE": "test"},
        workdir="/workspace",
        timeout=60,
    ),
)

run

Pulls an OCI image if needed and runs a command in it. Local only.

const result = await machine.run(
  "python:3.12-alpine",
  ["python", "-c", "print(40 + 2)"],
);
result = machine.run(
    "python:3.12-alpine",
    ["python", "-c", "print(40 + 2)"],
)

For cloud, set MachineConfig.image during creation and use exec().

Stream output

execStream() / exec_stream() yields stdout, stderr, exit, and error events as they arrive. It is supported on the local target. For cloud command streaming, use the Cloud REST API’s Server-Sent Events response.

for await (const event of machine.execStream(["sh", "-lc", "make test"])) {
  if (event.kind === "stdout" || event.kind === "stderr") {
    process.stdout.write(event.data);
  }
}
for event in machine.exec_stream(["sh", "-lc", "make test"]):
    if event["kind"] in ("stdout", "stderr"):
        print(event["data"], end="")

ExecResult

TypeScriptPythonMeaning
exitCodeexit_codeProcess exit code
stdout, stderrstdout, stderrUTF-8 text output
stdoutBytes, stderrBytesstdout_bytes, stderr_bytesByte output
stdoutTruncated, stderrTruncatedstdout_truncated, stderr_truncatedWhether cloud text output hit its limit
successsuccessExit code is zero
outputoutputCombined text output
assertSuccess()assert_success()Raise ExecutionError on a nonzero exit

Cloud responses can truncate captured text output. Check the truncation fields and use streaming execution or a file for large output.

Read and write files

Supported on local and cloud:

await machine.writeFile("/tmp/config.json", '{"enabled":true}');
const data = await machine.readFile("/tmp/config.json");
machine.write_file("/tmp/config.json", '{"enabled": true}')
data = machine.read_file("/tmp/config.json")

writeFile() / write_file() accepts an optional numeric file mode.

Images

Local only:

TypeScriptPythonResult
await machine.pullImage(image)machine.pull_image(image)Pulled ImageInfo
await machine.listImages()machine.list_images()Cached ImageInfo objects

ImageInfo contains reference, digest, size, architecture, and os.

Ports and ingress

url

url() returns the public ingress URL for the first published cloud port. It returns null / None for local machines, machines without a published port, or cloud machines whose host port is not allocated.

endpoint

Cloud only. Builds authenticated HTTP and WebSocket connection details for a published guest port without making a request:

const { httpUrl, wsUrl, headers } = machine.endpoint(8080, "/healthz");
endpoint = machine.endpoint(8080, "/healthz")
print(endpoint.http_url, endpoint.ws_url, endpoint.headers)

HTTP convenience methods

const response = await machine.fetch(8080, "/healthz");
body = machine.request(
    8080,
    "/healthz",
    method="GET",
    data=None,
    timeout_s=30,
)

The port must be declared in MachineConfig.ports, and the service must listen on the guest port.

Lifecycle

TypeScriptPythonBehavior
await machine.stop()machine.stop()Stops the machine without deleting storage
await machine.delete()machine.delete()Stops the machine and permanently deletes its storage

Use try / finally in TypeScript:

const machine = await Machine.create(undefined, { target: "local" });
try {
  await machine.exec(["echo", "work"]);
} finally {
  await machine.delete();
}

Python Machine is a context manager:

from smol import ConnectOptions

with Machine.create(conn=ConnectOptions(target="local")) as machine:
    machine.exec(["echo", "work"])

Forking

The SDK exposes fork() for cloud machines. Create the source machine with forkable: true, then clone its live state:

const clone = await golden.fork("clone-1");
clone = golden.fork("clone-1")

Pass optional port mappings when a clone needs pinned host ports:

const clone = await golden.fork("clone-1", [{ host: 18080, guest: 8080 }]);
from smol import PortSpec

clone = golden.fork(
    "clone-1",
    ports=[PortSpec(host=18080, guest=8080)],
)

When ports are omitted, the control plane allocates fresh host ports so concurrent clones do not collide.

Cloud forks are node-local and require a forkable source machine. They do not provide portable snapshots or live migration between local and cloud. For local forks, use the smolvm CLI.

Configuration types

ConnectOptions

TypeScriptPythonDescription
targettarget"local" or "cloud"; select explicitly when both are configured
baseUrlbase_urlCloud API base URL
apiKeyapi_keyCloud API key

Cloud authentication also reads SMOL_CLOUD_TOKEN. The base URL override is SMOL_CLOUD_URL.

MachineConfig

TypeScriptPythonTargetDescription
namenameBothName; generated when omitted
imageimageBothRequired for cloud; optional for local
mountsmountsLocalHost-directory mounts
portsportsBothHost-to-guest or published port mappings
resourcesresourcesBothCPU, memory, disk, network, and local GPU settings
persistentpersistentLocalKeep the local machine record
autoStopSecondsauto_stop_secondsCloudStop after an idle period
ttlSecondsttl_secondsCloudDelete after a fixed period
forkableforkableCloudPrepare as a live fork base
envenvCloudWorkload environment at creation
workdirworkdirCloudWorkload working directory at creation

ResourceSpec

TypeScriptPythonTargetDescription
cpuscpusBothvCPU count; omitted values use the target’s default
memoryMbmemory_mbBothMemory in MiB; omitted values use the target’s default
networknetworkBothGuest outbound networking; default false
storageGbstorage_gbBothStorage disk size in GiB; local SDK default 20
overlayGboverlay_gbLocalOverlay disk size in GiB; local SDK default 10
allowHostsallow_hostsCloudEnable networking restricted to these hostnames and subdomains
allowCidrsallow_cidrsCloudEnable networking restricted to these IP ranges
gpugpuLocal VulkanEnable virtio-gpu/Venus; default false
gpuVramMibgpu_vram_mibLocal VulkanVRAM allocation; omitted values use the engine default
cudacudaLocal CUDA remotingEnable CUDA API remoting; default false

MountSpec

Local host-directory mount:

{
  source: "/absolute/host/path",
  target: "/workspace",
  readOnly: false,
}
MountSpec(
    source="/absolute/host/path",
    target="/workspace",
    read_only=False,
)

Set readOnly / read_only explicitly for every mount. Use read-only mounts for source code and writable mounts only for directories the guest must change.

PortSpec

{ host: 8080, guest: 8080 }
PortSpec(host=8080, guest=8080)

Python AsyncMachine

Use AsyncMachine when synchronous calls would block an event loop:

import asyncio
from smol import AsyncMachine, ConnectOptions, MachineConfig

async def main():
    async with await AsyncMachine.create(
        MachineConfig(image="alpine:3.20"),
        ConnectOptions(target="cloud"),
    ) as machine:
        result = await machine.exec(["echo", "hello"])
        print(result.stdout)

asyncio.run(main())

It mirrors the synchronous API with awaitable I/O methods. endpoint() remains synchronous because it only builds connection data.

Errors

SDK errors derive from SmolError and include a machine-readable code.

  • ExecutionError: a command assertion failed
  • NotSupportedError: the selected target does not support the operation
  • InvalidConfigError: the configuration is invalid