Pin and audit the image
Pin an image digest, scan it, use a non-root user where possible, and keep the ACP executable and repository tools at reviewed versions.
This is the canonical three-provider composition: Codex supplies the <Agent /> session, Docker supplies disposable execution, and S3 supplies durable <Workspace /> revisions.
application ├─ codexAgent() → codex-acp → Codex model session ├─ dockerSandbox() → named image → disposable container └─ s3Workspace() → local staging ↔ S3 revisions
data: S3 revision → local materialization → /workspace bind mountauth: process environment / workload identity → Codex and S3 clientsThe example is complete at the AML boundary. It assumes that your application has already built and published an image containing codex-acp, Codex’s runtime dependencies, a shell, Node, and the repository tools your Agent is expected to use.
import { S3Client } from "@aws-sdk/client-s3"import { Agent, AmlRuntime, Sandbox, Workspace, codexAgent, dockerSandbox, s3Workspace } from "@aml-jsx/sdk"
const bucket = process.env.AML_WORKSPACE_BUCKETconst region = process.env.AWS_REGION ?? "us-east-1"const codexApiKey = process.env.CODEX_API_KEY ?? process.env.OPENAI_API_KEY
if (bucket === undefined || bucket.length === 0) { throw new Error("AML_WORKSPACE_BUCKET is required")}
if (codexApiKey === undefined || codexApiKey.length === 0) { throw new Error("CODEX_API_KEY or OPENAI_API_KEY is required")}
// Application-owned client. In production, prefer workload identity or the// AWS SDK's default credential chain over static keys in environment files.const workspaceClient = new S3Client({ region })
const runtime = new AmlRuntime({ agentProvider: codexAgent({ apiKey: codexApiKey, command: "codex-acp", model: "gpt-5.6-luna", reasoningEffort: "low", }), sandboxProvider: dockerSandbox({ // PLACEHOLDER: publish and pin an image containing codex-acp, Codex's // runtime, Node, sh, and the repository tools this workflow needs. image: "REGISTRY.example/aml-codex-runtime@sha256:IMAGE_DIGEST", }), workspaceProvider: s3Workspace({ bucket, client: workspaceClient, format: "archive", prefix: "production/aml-workspaces", }),})
const result = await runtime.evaluate( <Workspace id="review-42" load={{ revision: "current", exclude: ["node_modules/**", ".git/**"] }} save={{ on: "success", gitignore: true, retention: 5 }} > <Sandbox access="read-write"> <Agent system="Review the repository and write reports/summary.md with evidence and actionable findings."> Inspect the checked-out project, run only the approved repository tools, and produce a concise review report. </Agent> </Sandbox> </Workspace>)
console.log(result)AML invokes the configured command inside the effective Sandbox. For this composition, the command must be present in the image, not merely installed on the host running the Node process.
# PLACEHOLDER: choose and audit your own base image and package versions.FROM node:26-bookworm
RUN apt-get update \ && apt-get install --yes --no-install-recommends ca-certificates git openssh-client \ && rm -rf /var/lib/apt/lists/*
# Install the exact ACP adapter and native Codex versions approved by your deployment.RUN npm install --global \ @agentclientprotocol/codex-acp@1.4.0 \ @openai/codex@0.147.0
# Add the repository-specific compilers, linters, and scripts here.WORKDIR /workspaceENTRYPOINT ["sh"]The Docker provider starts a detached container with --rm, bind-mounts the materialized Workspace at /workspace, sets the logical working directory, and starts a keepalive shell. It does not install the Agent during acquisition. A node:26 image by itself is therefore not a Codex image.
Build and smoke-test the image before publishing it. Replace the registry and tag with application-owned values:
docker build --pull --tag registry.example/aml-codex-runtime:reviewed .docker run --rm --entrypoint sh registry.example/aml-codex-runtime:reviewed -lc \ 'command -v codex-acp && command -v codex && command -v node && command -v git'docker push registry.example/aml-codex-runtime:revieweddocker inspect --format '{{index .RepoDigests 0}}' registry.example/aml-codex-runtime:reviewedUse the resulting repository digest in dockerSandbox({ image }). The smoke test proves only that required executables resolve; run a credentialed ACP smoke evaluation in the deployed network and identity environment before production traffic.
The three providers have different ownership boundaries:
| Boundary | Codex | Docker | S3 Workspace |
|---|---|---|---|
| Owns | ACP process, model session, Agent configuration | Container, process transport, image execution | Revisions, lock, materialization, publication |
| File view | Uses the Sandbox cwd | Same-host bind mount at /workspace | Downloads current revision to local temporary space |
| Write durability | None | Writes reach the host materialization immediately | Durable only after successful save and conditional index publication |
| Remote transfer | No | No; bind mount | Yes, during load and save |
Docker is not an archive-transfer provider in this composition. The S3 provider first materializes a revision locally; Docker then mounts that local directory. On successful completion, S3 snapshots the local directory, uploads an immutable revision, conditionally publishes workspace.json, and prunes beyond retention.
review-42 from S3. With the default lock={true}, S3 creates or renews production/aml-workspaces/review-42/lock.json.current state is an application decision; initialize a new Workspace explicitly when appropriate./workspace.codex-acp in the container. The Codex profile supplies isolated CODEX_HOME, model configuration, filesystem mode, and the API-key authentication method./workspace. The Agent’s permission mode does not add Docker network, capability, user, or resource isolation.Pin and audit the image
Pin an image digest, scan it, use a non-root user where possible, and keep the ACP executable and repository tools at reviewed versions.
Harden Docker outside AML
Configure network egress, capabilities, seccomp/AppArmor, CPU/memory/PID/disk limits, daemon access, and secret
delivery in the deployment platform. dockerSandbox() does not configure these controls.
Constrain S3
Scope GetObject, PutObject, DeleteObject, and ListBucket to the environment prefix. Verify conditional
writes, stable ETags, streaming bodies, and pagination with the exact S3-compatible service. Start from the provider
guide’s minimum IAM policy.
Treat model output as data
Validate paths, commands, structured results, and generated files in application code. A prompt or Codex read-only mode is not a hostile-code boundary.
Do not use save={{ on: "always" }} as crash-safe checkpointing. It can publish a partial snapshot after descendant failure, but cancellation skips saving and a process failure can still lose edits that were not published.
| Symptom | Evidence to collect | Likely boundary |
|---|---|---|
codex-acp cannot start | Run the exact command in the image; inspect image PATH, architecture, and runtime dependencies | Docker image or ACP installation |
| Codex authenticates locally but not in Docker | Confirm the credential is injected into the container launch environment and is not only present on the host | Application secret delivery / image boundary |
| Files are visible during the run but absent from S3 | Check whether evaluation reached save, whether publication returned a conditional conflict, and whether release also failed | Workspace save/publication |
A second run receives AML_WORKSPACE_CONFLICT | Identify the active owner and wait for it to release; do not delete a lock based only on age unless the provider’s stale policy proves it | S3 writer lease |
| Docker remains after cancellation | Inspect the named aml-<evaluation-prefix>-<uuid> container and daemon events, then remove only a container verified to belong to the failed evaluation | Docker cleanup |