> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Lifecycle and long-running sandboxes

> Timeouts, no-limit sandboxes, pause and resume.

```mermaid theme={null}
stateDiagram-v2
  [*] --> running: create
  running --> paused: pause, idle, or timeout with on_timeout=pause
  paused --> running: connect / resume
  running --> [*]: delete, or timeout (default)
  paused --> [*]: delete
```

## Timeouts

Every sandbox has a timeout: the number of seconds from now until its timeout action runs. The action is **delete** by default, or **pause** if you ask for it. Each call to `set_timeout` replaces the previous value, counted from that moment.

| Where | Default |
| - | - |
| SDK `Sandbox.create()` | 300 s |
| CLI `create`, `run` | `--timeout 300s` |
| API `POST /v2/sandboxes` | 300 s |
| API `POST /sandboxes` (v1) | 15 s |

```python timeouts.py theme={null}
from anona import Sandbox

sbx = Sandbox.create("base", timeout=600)  # 10 minutes
sbx.set_timeout(3600)                      # now one hour from this call
print(sbx.get_info().end_at)
sbx.kill()
```

```typescript timeouts.ts theme={null}
import { Sandbox } from "@anona/sdk";

const sbx = await Sandbox.create("base", { timeoutMs: 600_000 }); // 10 minutes
await sbx.setTimeout(3_600_000);                                    // one hour from now
console.log((await sbx.getInfo()).endAt);
await sbx.kill();
```

```bash timeouts.sh theme={null}
SANDBOX_ID=$(anona-compute create -t base --timeout 10m --json | jq -r .sandboxID)
anona-compute timeout "$SANDBOX_ID" 1h
anona-compute delete "$SANDBOX_ID"
```

## No time limit

On plans without a maximum lifetime, a timeout of `-1` means **no deadline**: the sandbox runs until you pause or delete it. It reports `endAt` as `9999-12-31T23:59:59Z`, because the E2B schema requires a date. A sandbox without a deadline that you pause and resume keeps having no deadline.

```python no_limit.py theme={null}
from anona import Sandbox

sbx = Sandbox.create("base", timeout=-1)
print(sbx.get_info().end_at)  # 9999-12-31 23:59:59+00:00
sbx.kill()
```

```bash no_limit_api.sh theme={null}
# The REST API takes -1 directly; so do the dashboard and the MCP create_sandbox tool.
curl -sS https://api.compute.anonalabs.com/v2/sandboxes \
  -H "X-API-Key: $E2B_API_KEY" -H 'Content-Type: application/json' \
  -d '{"templateID": "base", "timeout": -1}' | jq -r .sandboxID > sandbox.id
curl -sS -X DELETE "https://api.compute.anonalabs.com/sandboxes/$(cat sandbox.id)" -H "X-API-Key: $E2B_API_KEY"
```

A plan with a maximum lifetime (the free plan's is one hour) refuses `-1` and any longer timeout with `400`, naming the limit. The `anona-compute` CLI takes positive durations only.

> [!WARNING]
>
> A sandbox without a deadline is billed for as long as it runs. Let it [pause when idle](#pause-when-idle): paused sandboxes cost only snapshot storage.

## Pause and resume

Pausing snapshots the sandbox's memory and disk and stops the VM. Resuming restores it: running processes, open files and `/dev/shm` carry on where they were. Paused sandboxes are kept until you delete them; they count against your plan's paused-sandbox limit, and their snapshot storage is billed per GB-month.

```python pause_resume.py theme={null}
from anona import Sandbox

sbx = Sandbox.create("base")
sbx.commands.run("echo kept > /dev/shm/marker")
sbx.pause()

sbx = Sandbox.connect(sbx.sandbox_id)  # resumes a paused sandbox
print(sbx.commands.run("cat /dev/shm/marker").stdout)
sbx.kill()
```

```typescript pause_resume.ts theme={null}
import { Sandbox } from "@anona/sdk";

const sbx = await Sandbox.create("base");
await sbx.commands.run("echo kept > /dev/shm/marker");
await sbx.pause();

const again = await Sandbox.connect(sbx.sandboxId); // resumes a paused sandbox
console.log((await again.commands.run("cat /dev/shm/marker")).stdout);
await again.kill();
```

```bash pause_resume.sh theme={null}
SANDBOX_ID=$(anona-compute create -t base --json | jq -r .sandboxID)
anona-compute pause "$SANDBOX_ID"
anona-compute list --state paused
anona-compute resume "$SANDBOX_ID"
anona-compute delete "$SANDBOX_ID"
```

On the engine's test hosts, a pause took 0.1 to 0.3 s and a resume 0.8 to 0.9 s, with memory, uptime and a detached process kept. Production numbers are not published yet.

## Pause on timeout

Ask for pause instead of delete when the timeout runs out. The sandbox then waits, paused, until you resume it:

```python pause_on_timeout.py theme={null}
from anona import Sandbox

sbx = Sandbox.create("base", timeout=3600, lifecycle={"on_timeout": "pause"})
print(sbx.get_info().state)
sbx.kill()
```

```typescript pause_on_timeout.ts theme={null}
import { Sandbox } from "@anona/sdk";

const sbx = await Sandbox.create("base", { timeoutMs: 3_600_000, lifecycle: { onTimeout: "pause" } });
console.log((await sbx.getInfo()).state);
await sbx.kill();
```

```bash pause_on_timeout.sh theme={null}
SANDBOX_ID=$(anona-compute create -t base --timeout 1h --auto-pause --json | jq -r .sandboxID)
anona-compute info "$SANDBOX_ID"
anona-compute delete "$SANDBOX_ID"
```

`auto_resume` (resume when a request reaches the sandbox's URL) is accepted with pause-on-timeout, but waking on traffic is **Coming soon**: a request to a paused sandbox's URL gets `409` today. Resume with `connect`.

## Pause when idle

A sandbox can pause itself once nothing has used it for a while: no request to any of its ports, no open terminal, live view, SSH session or byte pipe, and no running process started through `envd`. Starting to run again (resume) restarts the clock. This is what the dashboard's lifetime **No limit (pauses when idle)** sets up, with 15 minutes.

```python idle_pause.py theme={null}
import os
import httpx
from anona import Sandbox

sbx = Sandbox.create("base", timeout=-1)  # no deadline ...
api = httpx.Client(base_url="https://api.compute.anonalabs.com", headers={"X-API-Key": os.environ["E2B_API_KEY"]})
r = api.put(f"/v1/sandboxes/{sbx.sandbox_id}/auto-pause", json={"idleMinutes": 15})  # ... but pause after 15 idle minutes
r.raise_for_status()
print(r.json())  # {"sandboxID": ..., "allowPublicTraffic": ..., "publicPorts": [], "idlePauseMinutes": 15}
sbx.kill()
```

```bash idle_pause_api.sh theme={null}
# Or at create time, over the API: autoPause takes {"idleMinutes": n} (0 = off; {} = the team default, else 15).
curl -sS https://api.compute.anonalabs.com/v2/sandboxes -H "X-API-Key: $E2B_API_KEY" -H 'Content-Type: application/json' \
  -d '{"templateID": "base", "timeout": -1, "autoPause": {"idleMinutes": 30}}' | jq -r .sandboxID > sandbox.id
curl -sS "https://api.compute.anonalabs.com/v1/sandboxes/$(cat sandbox.id)/access" -H "X-API-Key: $E2B_API_KEY" | jq .idlePauseMinutes
curl -sS -X DELETE "https://api.compute.anonalabs.com/sandboxes/$(cat sandbox.id)" -H "X-API-Key: $E2B_API_KEY"
```

`idleMinutes` ranges from 1 to 10080 (one week); `0` turns it off. The SDK's `lifecycle={"on_timeout": "pause"}` (`autoPause: true` on the wire) pauses at the timeout, and idles only if your organization has a default idle time. Resume a paused sandbox with `Sandbox.connect(id)`.

## Delete

Delete a sandbox with `anona-compute delete`, the dashboard, or `kill()` in the SDK: the SDK keeps the E2B method name, so code written for E2B works unchanged. It returns as soon as the gateway has the request (it waits up to 2 s for the engine) and finishes in the background with retries. The sandbox disappears from `list` at once.

## Fork

Fork copies a running sandbox - memory, files and running processes - into new sandboxes. Each copy starts from the exact state the original was in, which is the point: get an agent to an interesting state once, then try ten things from there.

The original is **briefly frozen** while its memory is written (about 0.1 s, longer right after it has touched a lot of memory), then keeps running on its own node with the same ID and the same deadline. The snapshot is taken once however many copies you ask for.

```python fork.py theme={null}
from anona import Sandbox

sbx = Sandbox.create("base", timeout=300)
sbx.files.write("/root/state.txt", "reached an interesting state")
sbx.commands.run("nohup sh -c 'while true; do date >> /root/ticks; sleep 1; done' >/dev/null 2>&1 &")

# Each entry is a Sandbox or the exception that stopped that one fork.
forks = sbx.fork(count=3, timeout=300)
for f in forks:
    if isinstance(f, Exception):
        print("fork failed:", f)
        continue
    print(f.sandbox_id, f.files.read("/root/state.txt"))   # the parent's file
    print(f.commands.run("wc -l < /root/ticks").stdout)    # the parent's process, still ticking
    f.kill()
sbx.kill()
```

```typescript fork.ts theme={null}
import { Sandbox } from "@anona/sdk";

const sbx = await Sandbox.create("base", { timeoutMs: 300_000 });
await sbx.files.write("/root/state.txt", "reached an interesting state");

const forks = await sbx.fork({ count: 3, timeoutMs: 300_000 });
for (const f of forks) {
  if (f instanceof Error) {
    console.log("fork failed:", f.message);
    continue;
  }
  console.log(f.sandboxId, await f.files.read("/root/state.txt"));
  await f.kill();
}
await sbx.kill();
```

```bash fork.sh theme={null}
SANDBOX_ID=$(anona-compute create -t base --json | jq -r .sandboxID)
anona-compute exec "$SANDBOX_ID" -- sh -c 'echo "reached an interesting state" > /root/state.txt'
anona-compute fork "$SANDBOX_ID" --count 3 --timeout 5m   # one sandbox ID per line
anona-compute delete "$SANDBOX_ID"
```

Each fork is a new sandbox and behaves like one: it takes a slot of your concurrent-sandbox limit, it is **billed like any other sandbox** for as long as it runs, and it inherits the original's template, metadata, timeout policy and network policy, with the lifetime you pass to `fork`.

A fork of ten on a plan with room for three is not an all-or-nothing failure. The copies that fit are created, and every other entry of the result says why it was not - `429` when your plan's limit is reached, `503` when the platform is at capacity. The SDK gives you those as exceptions in the same list, in request order. A non-`201` from the API means nothing was forked at all: the sandbox was not running (`409`), or there was no room for even one copy (`429`), in which case the original is not frozen either.

> [!NOTE]
>
> Forking needs the `sandboxes:create` scope, and an API key restricted to particular sandboxes cannot fork. In the dashboard, **Fork** on a running sandbox's page asks how many copies and how long they should live, shows what the batch will cost before you commit, and lists the outcome of each copy.

> [!WARNING]
>
> Each fork request keeps a **checkpoint** of the source sandbox, which the copies read from, and this engine cannot delete one yet - deleting the copies does not free it. So forking is capped per team (32 checkpoints by default); past that, `fork` answers `429` and says so. Ask support to raise it, and tell us what you are building with fork: the cap exists only until checkpoints can be reclaimed.

## Snapshots

**Coming soon:** named snapshots you can keep and create from later (`POST /sandboxes/{id}/snapshots`, `GET /snapshots`). Fork takes a snapshot for you and uses it immediately.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.