# Give an Agent a Bigger Sandbox Mid-Task Without Losing Its Work

> 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.

Source: https://sandboxer.com/guides/give-an-agent-a-bigger-sandbox-mid-task. Updated 2026-10-27.

**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](/guides/fix-exit-code-137-or-a-full-disk-in-a-sandbox). For telling a cold boot from a memory resume, see [detect a cold boot after resume](/guides/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:

```python
# ~/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:

```bash
sbx exec $id "python3 ~/task/task.py; echo exit=\$?; cat ~/task/state.json"
```

```text
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](/docs/long-running#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:

```bash
#!/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}'
done
```

Run on a fresh `small` sandbox with the task copied in:

```bash
time ./grow.sh $id
```

```text
run  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.605s
```

Some notes from the runs:

- Read the new size from `type` and `memoryGB`. In our run on October 27, 2026, `stats` briefly still held the sample from before the stop (`memoryTotalMb: 3914`, the `small` figure); 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](/guides/fix-exit-code-137-or-a-full-disk-in-a-sandbox) covers this).
- The free trial runs `small` and `default` only, and `xlarge` needs the $100 plan or higher ([machines](/docs/machines)), so cap `bigger` at the sizes you can use.
- Give the `sbx exec` a `--timeout` longer than the task. The default is 30 seconds. For tasks longer than a few minutes, use `sbx exec --detach` and poll, as in [run a long job](/guides/run-a-long-job-in-a-cloud-sandbox).

## 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`:

```bash
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}'
```

```text
{"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](/guides/move-files-in-and-out-of-a-sandbox)) |
| 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](/docs/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

- [Fix exit code 137 or a full disk in a sandbox](/guides/fix-exit-code-137-or-a-full-disk-in-a-sandbox)
- [Detect a cold boot after resume](/guides/detect-a-cold-boot-after-resume)
- [Fork a running sandbox](/guides/fork-a-running-sandbox-with-memory)
- [Machines and sizes](/docs/machines)
