Give an Agent a Bigger Sandbox Mid-Task Without Losing Its Work
Updated October 27, 2026 .md
Short answer: Yes, if the work is on disk. Stop the sandbox, then sbx resume --type with a bigger size. In our test that took about 6 seconds, and files came back while processes and memory did not (memoryRestored false, resumeReason resized). Have the agent checkpoint to disk so it can continue after the cold boot.
This page is about the agent's side of a resize: what its task needs to look like so a bigger machine is a short pause, not a restart from zero. For diagnosing exit code 137 or a full disk, see fix exit code 137 or a full disk. For telling a cold boot from a memory resume, see detect a cold boot after resume.
What survives a resize#
We ran all of this on prod on 2026-10-27: a small sandbox, a task killed halfway for lack of memory, then a stop and sbx resume --type default.
After sbx resume --type default |
|
|---|---|
| Files in your home directory (the checkpoint, the code) | Kept |
| Packages you installed, the disk's contents | Kept, and the disk grows to the new size (df showed 56G, 51G free) |
Running processes (the task, a python3 -m http.server we left running) |
Gone. Nothing listened on port 8000 after the resume |
| Anything only in memory: variables, caches, the agent's in-process state | Gone |
Files in /tmp |
Gone |
| Sandbox id, subdomain, URL | Kept (smooth-simple-tapir before and after) |
memoryRestored / resumeReason |
false / "resized" |
| Step | Time (one run each) |
|---|---|
sbx stop --wait |
2.2 s |
sbx resume --type default (until it answers commands) |
3.4 s |
sbx fork --type default --wait of the stopped sandbox |
3.4 s |
| The whole grow-and-continue loop below, including about 10 s of the task's own work | 18.6 s |
A resize is a stop and a cold boot. There's no way to add memory to a running sandbox, so the task has to survive losing its process.
Make the task checkpoint to disk#
The rule: after each unit of work, write what you've done to a file in the home directory, and on start, skip anything already recorded. Write the checkpoint atomically, so a kill in the middle of a write leaves the old file or the new one, never half of one. Here is the test task. Its embed step holds 5 GB at once, more than a small sandbox's 4 GB:
# ~/task/task.py: a multi-step job that checkpoints after every step.
import json, os, sys, time
STATE = os.path.expanduser("~/task/state.json")
STEPS = ["fetch", "parse", "index", "embed", "report"]
def load():
try:
with open(STATE) as f:
return json.load(f)
except FileNotFoundError:
return {"done": [], "results": {}}
def save(state):
tmp = STATE + ".tmp"
with open(tmp, "w") as f:
json.dump(state, f)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, STATE) # atomic: a crash leaves the old or the new file, never half of one
def run(step):
if step == "embed": # the heavy step: holds 5 GB at once
blocks = [bytearray(256 * 1024 * 1024) for _ in range(20)]
return f"{len(blocks) * 256} MB"
time.sleep(2) # stands in for real work
return "ok"
state = load()
for step in STEPS:
if step in state["done"]:
print(f"skip {step} (checkpointed)", flush=True)
continue
print(f"run {step}", flush=True)
state["results"][step] = run(step)
state["done"].append(step)
save(state)
print("all steps done", flush=True)On the small sandbox, the kernel killed it at the heavy step. The three finished steps were already on disk:
sbx exec $id "python3 ~/task/task.py; echo exit=\$?; cat ~/task/state.json"run fetch
run parse
run index
run embed
exit=137
{"done": ["fetch", "parse", "index"], "results": {"fetch": "ok", "parse": "ok", "index": "ok"}}For a coding agent, the same idea means: commit or save work to files as you go, keep the plan and a progress log in a file (for example ~/task/PROGRESS.md), and put long-lived services in enabled systemd units so the cold boot starts them (services that should always run). The agent's conversation lives in your orchestrator, outside the sandbox, so it isn't affected. Only what the agent kept in the sandbox's memory is lost.
Let the orchestrator grow the sandbox and continue#
sbx exec returns the command's exit code, so the loop that drives the agent can react to 137 by moving up one size and running the task again:
#!/usr/bin/env bash
# grow.sh <sandbox-id>: run the task; if the kernel kills it for memory, move up one size and continue.
id=$1
bigger() { case $1 in small) echo default ;; default) echo large ;; large) echo xlarge ;; esac; }
while true; do
sbx exec "$id" --timeout 600 "python3 ~/task/task.py"
code=$?
[ "$code" -eq 137 ] || exit "$code" # 0 = done; anything else is not a memory problem
size=$(sbx info "$id" --json | jq -r .sandbox.type)
to=$(bigger "$size")
[ -n "$to" ] || { echo "already at the largest size"; exit 137; }
echo "OOM-killed on $size; moving to $to"
sbx stop "$id" --wait >/dev/null
sbx resume "$id" --type "$to" >/dev/null
sbx info "$id" --json | jq -c '.sandbox | {type, memoryGB, memoryRestored, resumeReason}'
doneRun on a fresh small sandbox with the task copied in:
time ./grow.sh $idrun fetch
run parse
run index
run embed
OOM-killed on small; moving to default
{"type":"default","memoryGB":8,"memoryRestored":false,"resumeReason":"resized"}
skip fetch (checkpointed)
skip parse (checkpointed)
skip index (checkpointed)
run embed
run report
all steps done
real 0m18.605sSome notes from the runs:
- Read the new size from
typeandmemoryGB. In our run on October 27, 2026,statsbriefly still held the sample from before the stop (memoryTotalMb: 3914, thesmallfigure); since a fix later that day, list views no longer show that stale sample after a resize. - Exit code 137 means SIGKILL, and memory is the usual cause, not the only one. If you want to be sure before paying for a bigger size, check
dmesg | grep -i 'out of memory'first (the 137 guide covers this). - The free trial runs
smallanddefaultonly, andxlargeneeds the $100 plan or higher (machines), so capbiggerat the sizes you can use. - Give the
sbx execa--timeoutlonger than the task. The default is 30 seconds. For tasks longer than a few minutes, usesbx exec --detachand poll, as in run a long job.
Resize in place, or fork a bigger copy#
A resize is one-way. Moving to a smaller size isn't supported yet: to go smaller, create a new sandbox of that size and copy your files. If you're not sure the agent should stay big, fork instead. A fork copies the disk into a new sandbox at the size you ask for, and leaves the original as it was.
We forked the stopped small sandbox (with its three-step checkpoint) to default:
sbx fork $id --type default --ttl 1800 --wait --json | tail -1 \
| jq -c '{event, id, type: .sandbox.type, memoryGB: .sandbox.memoryGB, memoryRestored: .sandbox.memoryRestored}'
sbx info $id --json | jq -c '.sandbox | {state, type}'{"event":"ready","id":"sb_dz2cxig1dv","type":"default","memoryGB":8,"memoryRestored":null}
{"state":"archived","type":"small"}The fork had the checkpoint, ran embed and report, and finished. The original stayed stopped at small. A fork's memoryRestored is null because a fork never carries memory. It took the source's name with (fork) appended; CLI builds from October 27, 2026 on accept sbx fork --name <name> to name it yourself.
Resize in place (resume --type) |
Fork a bigger copy (fork --type) |
|
|---|---|---|
| Keeps the sandbox id and URL | Yes | No: new id, new subdomain, new SSH host keys |
| Can go back to the old size | No | Yes: the original is untouched |
| Results end up | In the same sandbox | In the fork. Copy back what you need (see move files in and out) |
| Auto-stop | Keeps the sandbox's setting unless you pass --ttl |
Default one hour unless you pass --ttl |
| Cleanup | None | Delete whichever copy you don't need: both count toward your plan's included storage while stopped |
Use the fork when the bigger size is an experiment, or when only one step needs it and you want the rest of the task to go back to the cheaper size. Use the resize when the agent will need the memory for the rest of the task anyway.
What the bigger size costs#
Billing is per second while a sandbox runs, at the size it is running at, and a stopped sandbox costs nothing (billing). From the price list:
| Size | RAM | Per hour | 10 minutes | 2 hours |
|---|---|---|---|---|
small |
4 GB | $0.018 | $0.003 | $0.036 |
default |
8 GB | $0.036 | $0.006 | $0.072 |
large |
16 GB | $0.072 | $0.012 | $0.144 |
Each step up doubles the rate. Take an agent that runs 2 hours, where only a 10-minute step needs 8 GB. Running all 2 hours on default costs $0.072. Running on small and resizing to default when the heavy step starts at the 1-hour mark costs $0.018 for the first hour plus $0.036 for the second, $0.054 in total, because everything after the resize runs at the doubled rate. Forking a default copy for just the 10-minute step, then deleting it, keeps the rest at small: $0.033 for the 110 small minutes plus $0.006 for the fork, $0.039 in total. The roughly 6 seconds of stop and resume are not a meaningful cost.
FAQ#
Can I add memory without stopping the sandbox?#
No. The size changes only on resume or fork, and a resume at a new size always boots from disk. Plan for the task's process to end, and checkpoint to disk.
Can I use --require-memory to keep processes during a resize?#
No. --require-memory can't be combined with a size change, and the API answers 400 invalid_require_memory. The memory image belongs to the old size, so a resized sandbox always cold-boots.
Can the agent resize its own sandbox from inside?#
Not in one step, because stopping the sandbox ends the agent's process. The stop and resume have to come from something outside it, such as your orchestrator. Have the agent exit with a clear code or write a request to a file, and let the orchestrator do the resize.
Will the agent's open files and tmux sessions come back?#
No. Files on disk come back. Open file handles, shells, tmux panes and anything else in memory don't. /tmp was empty after our resize, so don't keep checkpoints there.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.