Skip to content

v0.21.0

Latest

Choose a tag to compare

@ggoodman ggoodman released this 02 Sep 20:13
641cbc5

Declarative blob storage

Blob-backed storage is now declared as part of a sandbox instead of acquired ahead of time. The same built-in storage driver can preserve an agent's root filesystem changes and provide a writable ext4 workspace:

import {
  defineSandbox,
  rootfs,
  SandboxBlobStorageError,
  storage,
} from "@torkbot/sandbox";
import { image as alpine323Agent } from "@torkbot/sandbox-image-alpine-3.23-agent";

const provider = {
  kind: "s3" as const,
  bucket: "agent-storage",
  region: "us-east-1",
  auth: { kind: "environment" as const },
};

const machine = storage.blob.overlay({
  provider,
  volume: "agent-42-machine",
});

const workspace = storage.blob.block({
  provider,
  volume: "agent-42-workspace",
  sizeBytes: 64n * 1024n * 1024n,
});

const sandbox = defineSandbox({
  rootfs: rootfs.cow({
    base: alpine323Agent,
    writable: machine,
  }),
});

await using vm = await sandbox.boot({
  mounts: { "/workspace": workspace },
  cwd: "/workspace",
});

These factory results are opaque declarations. boot() acquires their exclusive leases, and closing the VM flushes and releases them. If boot fails after acquiring only some resources, Sandbox releases everything it can before returning the error. Applications no longer need to coordinate acquisition handles or cleanup separately.

Blob overlays are bound to their base image on first use, and every volume is tagged as either a rootfs overlay or guest block device. Reusing a volume with another base image or in another role now fails before the VM starts instead of risking filesystem corruption.

Local filesystems, Amazon S3 and compatible services, Google Cloud Storage, and Azure Blob Storage are supported through the same provider shapes.

Breaking changes

The asynchronous block.blob.acquire() API and its caller-owned lifecycle handle have been removed. Replace it with storage.blob.block(...) and pass the declaration directly in boot({ mounts }). Use storage.blob.overlay(...) as the writable value for a blob-backed rootfs.cow(...).

This release also makes a deliberate storage-format break. Existing volumes from the acquisition API do not contain the role and base-image metadata required by the new safety checks, and earlier SlateDB volumes use a different object layout. Use a new volume name or provider prefix when upgrading.

Blob failures are now reported as SandboxBlobStorageError. volume-locked errors include retryAfterMs when Sandbox can determine a safe retry delay:

try {
  await using vm = await sandbox.boot({ mounts: { "/workspace": workspace } });
} catch (error) {
  if (
    error instanceof SandboxBlobStorageError
    && error.code === "volume-locked"
    && error.retryAfterMs !== undefined
  ) {
    console.log(`Retry in ${error.retryAfterMs}ms`);
  } else {
    throw error;
  }
}