# Sandbox lifecycle

Create, stop, start, archive and delete.

## Statuses

| Status | Meaning | What to do |
| --- | --- | --- |
| `creating` | The machine is being made. | Wait, and read it again. |
| `running` | Ready for commands. Billed by the second. | Use it. |
| `stopped` | Disk kept, processes gone. Costs 5% of its running price. With `memoryKept` true, its memory is kept too, and it costs 10%. | Start it to carry on. |
| `error` | The machine could not be made or started. | Read `error` on the sandbox. It says why. |
| `restoring` | An archived sandbox is being brought back. | Poll every 10 seconds. |
| `migrating` | Being moved to another server, disk intact. | Poll every 10 seconds. |

## Create

`POST /v1/sandboxes`

- `name` is lowercase letters, digits and hyphens, up to 63 characters, unique in your account. It cannot start or end with a hyphen.
- `size` is `tiny`, `small`, `medium` or `large`.
- The free plan makes tiny and small sandboxes, the $100 plan makes medium ones too, the $500 plan makes every size, the $2,000 plan makes every size. A size your plan does not make is refused with `402 size_not_on_plan`, and the message names the plan that makes it. See [Billing](https://agental.io/docs/billing).
- Allow up to 3 minutes. `201` is created, `200` is replayed, and `202` is not yet confirmed: wait 10 seconds and send the same request again.
- `snapshotId` starts the sandbox from one of your [snapshots](https://agental.io/docs/snapshots).

## Stop and start

`POST /v1/sandboxes/{id}/stop`

The disk is kept with every file, installed package and Docker image. Running processes and memory are not, unless you ask. Every stop returns its own `Stop-Id`.

## Stop with its memory kept

```http
POST /v1/sandboxes/sbx_2a9a47e50daec64a/stop
Content-Type: application/json

{ "keepMemory": true }
```

- The memory is kept as well as the disk, and the next start puts back every process as it was: shells, servers, a browser and what is in `/dev/shm`. Its clock is set again.
- Writing the memory takes about a second for each 110 MB the sandbox uses: some 15 seconds for a small using 1 GiB, up to 2 minutes for a large one that is full. Until then the sandbox is `running`, and billed as running. The stop answers `200` once it is written, or, still writing after 60 seconds, `202` with an `Operation-Id`: the stop goes on, and you read the sandbox until its `status` is `stopped`. The SDKs and the CLI do that for you.
- Stopped so, it costs 10% of its running price, not 5%, and `memoryKept` is `true` on it.
- A stop for its time limit, for billing or for a move never keeps memory. A sandbox that keeps its memory is not archived. Restoring a snapshot while it is stopped lets the memory go.
- If a start cannot put the memory back, as after an upgrade of the server it is on, the sandbox starts from its disk, and `memoryLost` says why.
- In the SDKs: `sandbox.stop({ keepMemory: true })` and `sandbox.stop(keep_memory=True)`. On the command line: `agental stop <id> --keep-memory`.

`POST /v1/sandboxes/{id}/start`

Brings the same machine back, usually in under a minute. Nothing that was running is started again for you, unless its stop kept its memory: then it comes back in seconds, as it was.

## Time limit

A sandbox runs until it is stopped. Give it a time limit when you make it, with `ttlSeconds` in the create, or in its [template's start](https://agental.io/docs/templates) for every sandbox made from it, and it stops itself once it has run that long since its last start. `autoStopAt` says when:

```shell
curl -X POST https://agental.io/v1/sandboxes \
  -H "Authorization: Bearer $AGENTAL_API_KEY" \
  -H "Idempotency-Key: long-job-1" \
  -H "Content-Type: application/json" \
  -d '{"name":"long-job","ttlSeconds":14400}'
```

`PATCH /v1/sandboxes/{id}` with `ttlSeconds` changes it later, for every start from then on. The most you can ask for is `86400` (24 hours). A command sent to a stopped sandbox answers `409 sandbox_not_running`.

## Move the stop

A job that needs more time, or less, moves the stop of the run under way, to a number of seconds from now or to a time:

```shell
curl -X PUT https://agental.io/v1/sandboxes/{id}/auto-stop \
  -H "Authorization: Bearer $AGENTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"inSeconds":14400}'
```

- It is at least 60 seconds from now, and at most `86400` (24 hours). Ask again to go on: a sandbox runs for as long as something keeps moving its stop.
- `GET /v1/sandboxes/{id}/auto-stop` says when it stops, the `secondsLeft`, and `setBy`: `ttl` for its last start and `ttlSeconds`, `account` for a move made with one of your keys or the console, and `sandbox` for one the sandbox made itself.
- It holds until the sandbox stops. The next start goes by `ttlSeconds` again, and so does the run once `ttlSeconds` is changed.
- A key needs the action `sandbox.auto-stop` for it.

Take the time limit away, and it runs until it is stopped, this run and every start after:

```shell
curl -X DELETE https://agental.io/v1/sandboxes/{id}/auto-stop \
  -H "Authorization: Bearer $AGENTAL_API_KEY"
```

A key needs `sandbox.update` for that, and a sandbox's own token cannot: an agent in it moves its stop, and you decide whether it has one.

## From inside the sandbox

An agent in the sandbox moves its own stop with no API key. `AGENTAL_SANDBOX_TOKEN`, in every command and SSH shell, is the sandbox's own token:

```shell
curl -s -X PUT "$AGENTAL_API_URL/v1/sandboxes/$AGENTAL_SANDBOX_ID/auto-stop" \
  -H "Authorization: Bearer $AGENTAL_SANDBOX_TOKEN" -H "Content-Type: application/json" \
  -d '{"inSeconds": 14400}'
```

- It reads and moves this sandbox's stop, and nothing else: any other call answers `403 sandbox_token_forbidden`, and another sandbox's stop `404`. It is for the run under way: it stops working when the sandbox stops, and each start gives the sandbox a new one. A fork, or a sandbox made from a snapshot, has a token of its own.
- Anyone who can run a command in the sandbox can read it: a key with `exec`, `ssh`, `desktop` or `agent.prompt` on a sandbox can move its stop too.
- It moves a stop the sandbox has, and never gives it one: on a sandbox with no time limit it answers `409 no_time_limit`. Whether a sandbox has a time limit is yours to say.
- Each move it makes is the event `sandbox.auto_stop_set` with `by: "sandbox"`, and `GET /v1/sandboxes/{id}/auto-stop` says `setBy: "sandbox"` until the next move. Your own move, or a `PATCH` of `ttlSeconds`, takes its place.
- The agents in the sandbox are told how, in the note and the `agental` skill they read. A sandbox held to an outbound rule that does not let it reach this API cannot make the call.

## Archiving

- A sandbox stopped for 6 hours is archived: its disk moves to storage. It is still `stopped`, costs what a stopped one costs, and `archived` is `true`.
- Starting an archived sandbox answers `202`. Poll `GET /v1/sandboxes/{id}` every 10 seconds until `status` is `running`.
- Files and forks of an archived sandbox answer `409 sandbox_archived` until it is running again.
- `POST /v1/sandboxes/{id}/archive` archives a stopped sandbox now.

## Delete

`DELETE /v1/sandboxes/{id}`

```http
DELETE /v1/sandboxes/sbx_2a9a47e50daec64a
X-Agental-Confirm-Delete: sbx_2a9a47e50daec64a
```

> **Warning:** Delete destroys the disk for good. Without the confirm header the API answers `confirm_delete_required` and changes nothing.

---

Every page of these docs as Markdown: [llms.txt](https://agental.io/llms.txt)
