Skip to content

Run AML in a Sandbox image

Resource-backed Docker workflow

Run a deterministic <Script /> inside the image selected by dockerSandbox(), then read the file it created after the disposable container is released.

This recipe deliberately omits image first. Docker Sandbox therefore uses AML’s recommended wearesingular/aml-agent-sandbox:latest image. The same default applies to Daytona and Modal when their provider factory receives no image or snapshot override.

  • Node.js >=26, ESM TypeScript/TSX execution, and @aml-jsx/sdk;
  • Docker running locally and able to pull wearesingular/aml-agent-sandbox:latest;
  • no model credentials—the recipe executes a deterministic Script rather than an Agent session.
import { mkdtemp, readFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { AmlRuntime, Sandbox, Script, Workspace, dockerSandbox, localWorkspace } from "@aml-jsx/sdk"
const directory = await mkdtemp(join(tmpdir(), "aml-image-cookbook-"))
const user =
typeof process.getuid === "function" && typeof process.getgid === "function"
? `${process.getuid()}:${process.getgid()}`
: undefined
const sandbox = dockerSandbox({
// Docker bind mounts retain host ownership. Use the host identity when the
// platform exposes one; the image itself remains non-root by default.
...(user === undefined ? {} : { user }),
})
await new AmlRuntime({
sandboxProvider: sandbox,
workspaceProvider: localWorkspace({ directory }),
}).evaluate(
<Workspace id="image-cookbook" load={false} save={false}>
<Sandbox access="read-write">
<Script
command="sh"
args={[
"-lc",
"node --version > runtime.txt && npm --version >> runtime.txt && python --version >> runtime.txt 2>&1 && pip --version >> runtime.txt && jq --version >> runtime.txt",
]}
/>
</Sandbox>
</Workspace>
)
console.log(await readFile(join(directory, "runtime.txt"), "utf8"))

Save the source as recipe.tsx in a project configured as shown in Getting started, then run:

Terminal
npx vite-node recipe.tsx

The first run may pull the image. A successful run prints the installed Node.js, npm, Python, pip, and jq versions from inside the container. runtime.txt survives because Docker bind-mounts the local Workspace; the container itself is removed.

  1. dockerSandbox() receives no image, so it selects wearesingular/aml-agent-sandbox:latest.
  2. <Workspace /> materializes the temporary host directory and the Docker provider mounts it at /workspace.
  3. <Sandbox /> acquires a disposable container and <Script /> runs only through that active runtime.
  4. Docker removes the container after the subtree settles. The local Workspace remains application-owned.

The <Sandbox /> component has no image prop. Image identity belongs to the provider factory because Local has no image, Daytona can use a snapshot, and Modal loads images through its registry integration.

latest is useful for evaluation and examples. Pin a stable version or digest for repeatable deployments:

const sandbox = dockerSandbox({
image: "wearesingular/aml-agent-sandbox:X.Y.Z",
...(user === undefined ? {} : { user }),
})

An application can instead extend AML’s image with project dependencies or replace it entirely:

const sandbox = dockerSandbox({
image: "ghcr.io/your-organization/project-agent@sha256:IMAGE_DIGEST",
...(user === undefined ? {} : { user }),
})

The override must contain the programs the workflow launches. An Agent workflow also needs its ACP/native Agent executable chain; a generic Node or Python image is not automatically Agent-ready.

If an application always uses one Agent, derive its project image from that Agent’s variant instead of full. This Dockerfile adds the SQLite CLI to OpenCode’s image:

Dockerfile
FROM wearesingular/aml-agent-sandbox:X.Y.Z-opencode
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends sqlite3 \
&& rm -rf /var/lib/apt/lists/*
USER aml
WORKDIR /workspace

Build it locally:

Terminal
docker build --tag example/aml-opencode:1 .

Then pair the derived image with opencodeAgent():

import { AmlRuntime, dockerSandbox, opencodeAgent } from "@aml-jsx/sdk"
const runtime = new AmlRuntime({
agentProvider: opencodeAgent({
env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY! },
}),
sandboxProvider: dockerSandbox({
image: "example/aml-opencode:1",
}),
})

The image supplies OpenCode and sqlite3; opencodeAgent() supplies Agent configuration, and the Sandbox provider supplies container lifecycle and process execution. Keep credentials out of the Dockerfile and inject them at runtime.