Cloud Agents

Sandboxes

The isolated machine every agent session runs in. Define the environment in YAML, size compute, open the browser IDE, and preview ports.

Every session runs in its own sandbox: an isolated Linux machine created for that session, with the session's repositories already cloned, and destroyed when the session finishes.

What is in the sandbox

              the sandbox, created per session
┌─────────────────────────────────────────────────────────┐
│                                                         │
│  /sandbox            your repositories, cloned and      │
│                      checked out before the agent runs  │
│                                                         │
│  base tooling        Python 3.13, Node.js 22, git, gh,  │
│                      curl, a C/C++ toolchain            │
│                                                         │
│  your additions      image.dockerfile_append,           │
│                      image.setup, hooks, skills         │
│                                                         │
│  scoped credentials  a 1-hour GitHub token minted for   │
│                      this sandbox alone, plus the       │
│                      sandbox.variables the config names │
│                                                         │
└─────────────────────────────────────────────────────────┘
   destroyed when the session ends; credentials die with it

   persists outside the box: session records, logs, costs,
   conversation state, workspace snapshots

The base image builds on Python 3.13 and adds Node.js 22 with npm, git, the GitHub CLI (gh), curl, and a C/C++ toolchain (make, g++). Repositories are cloned before the agent starts, and gh is already authenticated against them. Skills load at startup from your repositories and the config.

Credentials are scoped and short-lived. The GitHub token (GH_TOKEN) is minted for that sandbox alone, lives one hour, and covers only the sandbox's repositories; the config can narrow it further, down to read-only (see Permissions). Model calls route through Ellipsis with a per-session key; a real provider key never enters the sandbox. Everything is revoked or expires when the sandbox is torn down.

The machine is ephemeral: it is destroyed after the session, so nothing an agent installs or breaks can leak into the next one. The session's data persists outside it: the full step timeline, token usage, and cost breakdown, the downloadable logs, the config snapshot it ran, and, for conversations, the conversation state and a workspace snapshot taken when the conversation goes idle, so the next message resumes with the working tree intact in a fresh sandbox.

Defining the environment

Three fields under sandbox define the environment, each with a different lifetime:

  • image.dockerfile_append appends RUN instructions onto the managed base image, before any repository exists. Use it for toolchain installs. Only RUN is accepted; FROM, USER, WORKDIR, ENTRYPOINT, and COPY fail validation.
  • image.setup is a shell script that runs after repositories are checked out, and everything it writes to disk is captured in the cached image. Use it for dependency installs, so agents start with node_modules/ or a virtualenv in place. Capped at 10 minutes. Files are captured but environment variables are not, so never write a secret to disk here.
  • hooks are shell scripts that run on every session, never cached: post_start before any repository work, post_clone after checkout. Use them for session-scoped setup such as authenticating a CLI. Each is capped at 5 minutes.

A hook or setup script that exits non-zero fails the session with exit status lifecycle_hook_failed: broken setup surfaces as a clear failure, not an agent working in a half-prepared sandbox.

sandbox.variables injects credentials as ordinary environment variables. Store the value once (agent variable set NPM_TOKEN=...), then name it in the config; only agents that name a variable receive it, and stored values are write-only, never readable back through the API or CLI (see Permissions).

A complete agent using all three:

ellipsis:
  version: v1
  name: Nightly migration checker
  description: Runs splitshift-api's migration checks against a staging schema

claude:
  system: |
    Run `make check-migrations` in splitshift-api. If it fails,
    identify the migration at fault and open an issue with the
    failing output and the likely fix.

trigger:
  type: cron
  schedule: "0 3 * * *"

sandbox:
  repositories:
    - name: splitshift-api
  variables:
    - name: DOPPLER_TOKEN
  image:
    dockerfile_append: |
      RUN curl -Ls https://cli.doppler.com/install.sh | sh
    setup: |
      cd splitshift-api && pip install -r requirements.txt
  hooks:
    post_start: |
      doppler configure set token "$DOPPLER_TOKEN"

budget:
  session: 1.50

The Doppler CLI and the dependencies bake into the cached image; post_start authenticates fresh each session, so the token never lands in a cached image. The full workflow, including storing the variable and deploying: Give agents the tools your build needs.

Ellipsis sets ELLIPSIS_SANDBOX_ID in every sandbox, and nowhere else. Scripts that need to know whether they are running on the Ellipsis cloud check that variable.

Compute

Every sandbox gets 1 vCPU, 4096 MiB of memory, and a one-hour session timeout. sandbox.compute changes any of them per agent:

sandbox:
  compute:
    cpu: 4              # 0.125 to 16 vCPU
    memory: 16GB        # 512MB to 64GB
    timeout: 15m        # 60s to 1h

memory takes a size string (512MB, 16GB; units are binary, so 1GB is 1024MB) and timeout a duration string (90s, 15m, 1h, or combined like 1h30m). Both also accept an object form that spells the units out as keys (memory: {gb: 16}, timeout: {minutes: 15}). A value outside the range fails config validation; nothing is clamped.

timeout caps the session's wall clock: a session that reaches it has its sandbox killed and fails. One hour is both the default and the maximum, because a sandbox's GitHub token lives one hour and a session never outlives its credential. The knob only shortens a session.

Size up for workloads that need it (a monorepo build, a large test suite), not by default. Compute is billed on the requested allocation over the sandbox's lifetime, so unused headroom still bills (see Billing).

IDE and ports

Open a live session's sandbox in a browser IDE: the same working tree the agent is editing, with a terminal. From the session page in the dashboard, click Open IDE while the session is active, or from the terminal:

$ agent session ide session_7Hq2mX4p
https://app.ellipsis.dev/sandboxes/sbox_2Kf8pQ4w

The URL is the sandbox page in your dashboard, gated on org membership: it grants nothing by possession, so it is safe to paste in a Slack message, and it opens the IDE for any org member who already has access.

Preview ports work the same way. When the agent (or you, from the IDE terminal) starts a dev server in the sandbox, open it in your browser from the session page's Ports menu or with:

$ agent session port session_7Hq2mX4p 3000
https://app.ellipsis.dev/sandboxes/sbox_2Kf8pQ4w?port=3000

Any TCP port serves; 3000, 5173, 8000, and 8080 are the quick picks in the dashboard menu. The preview renders while something in the sandbox listens on that port.

Both require a running sandbox. A session that has gone idle answers with an error telling you to send it a message first; a message wakes the conversation in a fresh sandbox, and the session page hands out the new sandbox's links.

IDE and port access is on by default and controlled per agent by ellipsis.ide. Lock a sensitive agent's sandbox shut:

ellipsis:
  ide: false

Testing the environment

Test an environment definition end to end before trusting it: start a session with --rebuild, which skips the image cache and provisions through a fresh full build, and give the agent a prompt that exercises the tools.

$ agent session start --config-file agents/nightly-migration-checker.yaml --rebuild --watch \
    "Verify this environment: run gh auth status, confirm the repositories are cloned, and print doppler --version."
✻ Session starting…
✻ Starting sandbox…
  ⎿ Fetching repositories… · Cloning splitshift-api @ 4c19e7d
  ⎿ Running setup… · Successfully installed fastapi-0.115.6 sqlalchemy-2.0.36 alembic-1.14.0
✓ Sandbox ready · splitshift-hq/splitshift-api · full build

Every session streams its provisioning: which repositories are cloning, each image.setup and hook output line, and which cache tier the start took. A broken install fails right there with its output in the session feed, and the full provisioning log persists past the sandbox as a download. The rebuilt image refreshes the cache, so your team's next session on this config starts warm.

Provisioning output is recorded and readable by your account, so keep setup from echoing secret values.

Caching

The only thing that carries over between sessions is the cached image the sandbox boots from: the repositories plus everything dockerfile_append and setup produced. It is cached per repository set, commit, and image definition:

  • A session at a commit that already has an image starts with everything in place, at no install cost.
  • A session at a new commit starts from the most recent image for the same repositories, checks out the new commit, and re-runs setup on top, so package managers install only what changed.
  • Changing dockerfile_append or setup rebuilds the image on the next session; after that, sessions reuse the new cached image.

Never reused: anything the agent installed or changed at run time, hook output, and credentials. Every session's tokens are minted fresh and die with its sandbox.