If you have read How it Works and still cannot tell where an
agent actually runs, what it costs, or which harnesses are “serverless”, this
page is the answer. It is the one place that states the execution model plainly.
The default is: no container
Which execution mode you get is decided by the agent class, not by a per-turn
setting:
- SDK agents run in-process.
AnthropicAgent and OpenAIAgent (and
HermesAgent) extend the AI SDK’s ToolLoopAgent; the opt-in ElizaAgent
wraps an elizaOS AgentRuntime in the same process. All of them make plain
HTTPS calls to a model provider. There is no subprocess and no
container — the agent’s “environment” is your own process. These are the only
agents that run unchanged inside a JS-only serverless runtime (a Cloudflare
Worker, a Vercel function).
- CLI / full-OS agents run as a child process.
ClaudeCodeAgent,
CodexAgent, AntigravityAgent, OpenCodeAgent, and every other CLI agent extend
BaseCliAgent, which spawns the vendor binary (claude, codex, opencode,
…) via node:child_process. By default that child process runs on the host,
in rootDir (this.cwd ?? options?.rootDir ?? process.cwd()) — the same machine
that is driving the run. There is no automatic per-turn container.
Neither mode spins up an isolated OS per turn. SDK agents are API calls in
your process; CLI agents are local child processes in rootDir. A full OS
environment is opt-in — you wrap the work in <Sandbox>.
Three common guesses — all reasonable, all wrong by default
Newcomers consistently guess the same three things. Here is the correction:
| Guess | Reality |
|---|
| ”Each agent turn spins up a fresh sandbox that pauses between turns.” | No — by default no agent is containerized at all. Containerization is an explicit <Sandbox> choice, and pause/resume is a provider feature you opt into. |
| ”OpenCode is serverless, Claude Code never is.” | Half right. OpenCode is not in-process serverless — it is still a spawned binary that needs a container; it just goes serverless via a cheap cold per-boundary container when its API keys come from the environment. Claude Code is “never” only in subscription mode; with an API key it is exactly as serverless as OpenCode. |
| ”The real axis is which vendor.” | The real axis is in-process SDK vs subprocess CLI, and within CLI, stateless (API key) vs stateful (subscription OAuth + resumable session on disk). |
What you pay for — three billing axes
There is no single “cost of running an agent.” There are three independent axes,
and a given run may touch one, two, or all three:
| Axis | What it is |
|---|
| Model tokens / subscription | The model provider bills per input/output token (SDK agents, or CLI agents in API-key mode), or draws down a subscription quota (Claude Pro/Max, ChatGPT Plus/Pro) in subscription mode. |
| Host compute | When a CLI agent runs locally (the default), it consumes the machine that is driving the run — your laptop, a CI runner, a long-lived server. |
| Sandbox / provider compute | Only when you opt into <Sandbox>: the provider’s container or VM wall-clock, from create → run → teardown (longer if kept warm or left idling), plus any provider storage. |
Opting into a full OS: <Sandbox>
<Sandbox> runs a child workflow (or a single step)
inside an isolated runtime — whole-graph, per-step, or mixed, your choice. It
renders as exactly one scheduler task per boundary (not per agent turn);
children never become parent-run tasks. The runtime is pluggable
(bubblewrap, docker, codeplane, cloudflare) or any custom provider you
register — e.g. the Freestyle VM example adapter.
The lifecycle at a <Sandbox> boundary is: create the sandbox when the task
starts → run the harness → capture a diff bundle of what changed → tear it down
(unless you keep it). Cleanup, idle timeout, and reuse are the provider’s
decision, not a Smithers-core per-turn guarantee. The Cloudflare provider, for
example, creates one container keyed ${runId}-${sandboxId}, defaults
cleanup: "destroy", and can keep the container or hold it warm with
keepAlive. A Freestyle VM exposes explicit start() / stop() / fork() /
delete() and idleTimeoutSeconds.
Running a CLI agent in a fresh per-turn container is a composition you
author, not a built-in switch: wrap the agent’s <Task> in a child workflow
behind <Sandbox provider={…}>. No agent adapter creates or pauses a container
on its own — BaseCliAgent always spawns on the local host.
Statefulness — warm vs cold containers
CLI agents keep credentials and resumable sessions on disk, which decides
whether a cold per-turn container is viable:
- API-key billing → cold containers are fine. Pass
apiKey and the agent
forwards ANTHROPIC_API_KEY / OPENAI_API_KEY; sessions are stateless per
turn, so a fresh (cold) container each turn works.
- Subscription billing → needs a warm/persistent environment.
ClaudeCodeAgent clears ANTHROPIC_API_KEY so the CLI bills your Claude
Pro/Max subscription, and it reads credentials materialized by a one-time
interactive /login at <CLAUDE_CONFIG_DIR>/.credentials.json. CodexAgent
mirrors this with CODEX_HOME / auth.json and bills the ChatGPT
subscription. A cold container has none of that on disk, so subscription mode
needs the credential/session directory to persist — a sticky/warm container or
a mounted credential volume.
Serverless compatibility at a glance
| Harness | Mode | Serverless verdict | Cost driver |
|---|
AnthropicAgent, OpenAIAgent, HermesAgent, ElizaAgent | In-process SDK | ✅ Agent is fully serverless — runs inside a Worker/function, no container | Model tokens |
OpenCodeAgent | CLI child process | ⚠️ Serverless via a cold per-boundary container (<Sandbox>) when keys come from env; a subscription opencode auth login puts credentials on disk like the others | Tokens + container wall-clock |
ClaudeCodeAgent — API-key mode (apiKey set) | CLI child process | ⚠️ Same as OpenCode: cold per-boundary container works | Tokens + container wall-clock |
ClaudeCodeAgent / CodexAgent — subscription mode (default) | CLI child process | ⚠️ Needs a warm/sticky container or mounted credential volume | Subscription quota + warm container time |
AntigravityAgent, PiAgent, KimiAgent, ForgeAgent, AmpAgent, others | CLI child process | ⚠️ Container required; warm vs cold depends on the CLI’s session/credential handling | Tokens/subscription + container time |
Not fully serverless end-to-end today. The DB layer (dialect + Cloudflare
Durable-Object-SQLite / D1 descriptors) and the SDK agents are Worker-native,
but the core engine that advances every run uses node:fs and
node:child_process (it materializes git/jj worktrees on a real filesystem),
and the gateway is a node:http server. So today the engine runs on a Bun
host with a filesystem (a container on ECS/Cloud Run/GKE/a VM). Two efforts are
in flight: the Node runtime (Vercel Node functions, Lambda) is closest —
it has fs + child_process, the engine module now imports cleanly under
plain Node, and the platform layer is injectable
(RunOptions.effectPlatformLayer accepts NodeContext.layer); a run now
completes end-to-end under plain Node (regression-tested: a compute-task
workflow on PGlite, with the worker-task dispatch path falling back to
in-memory message storage instead of bun-sqlite), and agent-task and gateway
validation under Node remain. Isolates (Cloudflare Workers, Vercel Edge) are further out and
also need the node:fs/node:child_process seam; a Worker can host the
storage + in-process-agent path today, but not the run driver.
”Sandbox” means three different things
This overloading is the single biggest source of confusion — name the sense you
mean:
- Tool sandbox — the built-in tools (
read/write/edit/grep/bash)
jailed to rootDir, with symlinks/network/timeouts denied by default. A
path/permission jail, not an OS boundary.
- A CLI agent’s own internal policy — e.g. Codex’s
sandbox: "read-only" | "workspace-write" | "danger-full-access"
(seatbelt/seccomp inside the vendor binary). Passed straight through to the
CLI; unrelated to the other two.
- The
<Sandbox> component — compute isolation: a
provider-backed container/VM that runs a child workflow. This is the only one
that gives an agent “a real computer.”
Read next