Sandboxes
The isolated machine every agent session runs in. Define the environment in YAML and size compute per agent.
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 │
│ environment.variables the config │
│ names │
│ │
└─────────────────────────────────────────────────────────┘
destroyed when the session ends; credentials die with it
persists outside the box: session records, logs, costs,
conversation state, workspace snapshotsThe 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 and lives one hour; the config narrows it, down to read-only access on exactly the repositories you list (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 environment define the environment, each with a different lifetime:
image.dockerfile_appendappendsRUNinstructions onto the managed base image, before any repository exists. Use it for toolchain installs. OnlyRUNis accepted;FROM,USER,WORKDIR,ENTRYPOINT, andCOPYfail validation.image.setupis 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 withnode_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.hooksare shell scripts that run on every session, never cached:post_startbefore any repository work,post_cloneafter 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.
environment.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 secret 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 * * *"
environment:
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.50The 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. environment.compute changes any of them per agent:
environment:
compute:
cpu: 4 # 0.125 to 16 vCPU
memory: 16GB # 512MB to 64GB
timeout: 15m # 60s to 1hmemory 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).
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 buildEvery 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
setupon top, so package managers install only what changed. - Changing
dockerfile_appendorsetuprebuilds 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.
Conversations
Every mention and every session you start is a durable conversation. Follow-ups land in the same conversation with full context, not a fresh agent.
Skills
Teach every agent your team's conventions once, as Claude Code skills. Repo skills load automatically; the config's skills list attaches more.