Pause and resume¶
pause() freezes the whole sandbox into a memory snapshot and resume()
wakes it up just as it was: processes, terminals, files and the kernel
variables are still alive on the other side.
When to use it¶
- An agent waiting for the user for minutes or hours: while paused it pays no compute, only the snapshot storage. For a coding agent it is the default option between turns: see Which one do I use?.
- Work with gaps: auto-suspend (on by default) pauses the sandbox after 300 s without traffic and resumes it with the next call.
- When not to: pauses shorter than ≈ 150 s. A suspend/resume cycle costs the same as ≈ 140 s of compute (Costs), so it saves no money.
- Nor for waits longer than 8 h: suspended time counts toward the
maximum lifetime and the sandbox ends anyway. Save
HOMEwith persistence (persist=) and create another VM when needed; for an agent, see point 2 of Which one do I use?.
Quick example¶
from rayito import Sandbox
with Sandbox.create() as sbx:
sbx.run_code("x = 42")
sbx.pause() # (1)!
sbx.resume()
print(sbx.run_code("x").text) # "42": the kernel kept its state
print(sbx.get_health().resume_generation) # 1
suspend-microvm. It takes ≈ 1.4 s;resume()≈ 0.4 s.
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create() as sbx:
await sbx.run_code("x = 42")
await sbx.pause()
await sbx.resume()
print((await sbx.run_code("x")).text) # "42"
print((await sbx.get_health()).resume_generation) # 1
asyncio.run(main())
from rayito.e2b import Sandbox
with Sandbox.create() as sbx:
sbx.run_code("x = 42")
sbx.pause() # (1)!
sbx.connect() # (2)!
print(sbx.run_code("x").text) # "42"
pause(keep_memory=False)isUnimplementedError: the pause always saves memory and disk.beta_pause()is an alias.- As in E2B,
connect()resumes; the class formSandbox.connect(sandbox_id, access_token=...)does it from another process.
Step by step¶
Manual pause¶
pause() asks AWS to suspend the MicroVM and waits for SUSPENDED. It
returns False if it was already suspended. Open streams (a background
command, a PTY, a watch_dir, a running cell) are cut with an orderly end
and reattach on their own the next time you read them after resuming.
resume() resumes the MicroVM, waits for the agent and records the new
generation (get_health().resume_generation). If a kernel did not survive,
get_health().kernel_state_lost reports it.
pause(wait=False) and resume(wait=False) (TypeScript: { wait: false })
request the transition and return without waiting for it.
Without a handle, the class forms do the same:
Sandbox.pause(sandbox_id) / Sandbox.resume(sandbox_id) (TypeScript:
Sandbox.pause(sandboxId) / Sandbox.resume(sandboxId)). They do not need
the access token, but they cannot wait for the agent either: resume only
waits for get-microvm to say RUNNING.
Continue from another process¶
connect() also resumes: sbx.connect() on the handle you already have,
or Sandbox.connect(sandbox_id, access_token=...) from another process (a
worker, an agent's next turn). It is the pattern of a conversation paused
between turns (Lifecycle: connect).
from rayito import Sandbox
sbx = Sandbox.create()
sandbox_id, token = sbx.sandbox_id, sbx.access_token
sbx.pause()
sbx.close() # (1)!
again = Sandbox.connect(sandbox_id, access_token=token) # (2)!
try:
print(again.commands.run("echo back").stdout)
finally:
again.kill()
- The sandbox stays suspended; only the local handle is released.
- It resumes and waits for the agent.
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
sbx = await AsyncSandbox.create()
sandbox_id, token = sbx.sandbox_id, sbx.access_token
await sbx.pause()
await sbx.close()
async with await AsyncSandbox.connect(sandbox_id, access_token=token) as again:
print((await again.commands.run("echo back")).stdout)
asyncio.run(main())
import { Sandbox } from "rayito";
const sbx = await Sandbox.create();
const { sandboxId, accessToken } = sbx;
await sbx.pause();
sbx.close(); // the sandbox stays suspended
await using again = await Sandbox.connect(sandboxId, { accessToken });
console.log((await again.commands.run("echo back")).stdout);
Auto-suspend and IdlePolicy¶
By default every sandbox is created with IdlePolicy(max_idle_seconds=300,
auto_resume=True): AWS suspends it after 300 s without traffic through its
endpoint, and the SDK's next call wakes it on its own (≈ 0.7 s).
If you do not pass idle and that 300 s window does not fit before the end
of the sandbox (timeout, or max_lifetime with on_timeout), the
default auto-suspend is turned off: it would never fire and the sandbox
ends at its timeout, as in E2B. That way Sandbox.create(timeout=120)
just works. With on_timeout="pause" the idle policy is what suspends with
no client, so instead of turning off it drops to 60 s (the minimum). An
idle you pass yourself is not adapted: if it does not fit, it is
InvalidArgumentException / InvalidArgumentError.
from rayito import IdlePolicy, Sandbox
with Sandbox.create(idle=IdlePolicy(max_idle_seconds=900)) as sbx: # (1)!
print(sbx.commands.run("echo awake").stdout)
with Sandbox.create(idle=None) as sbx: # (2)!
print(sbx.commands.run("echo never suspends").stdout)
- Suspends after 15 minutes without traffic.
- No auto-suspend: it bills compute until its
timeout.
import asyncio
from rayito import AsyncSandbox, IdlePolicy
async def main() -> None:
async with await AsyncSandbox.create(idle=IdlePolicy(max_idle_seconds=900)) as sbx:
print((await sbx.commands.run("echo awake")).stdout)
async with await AsyncSandbox.create(idle=None) as sbx:
print((await sbx.commands.run("echo never suspends")).stdout)
asyncio.run(main())
import { Sandbox } from "rayito";
{
await using sbx = await Sandbox.create({ idle: { maxIdleSeconds: 900 } });
console.log((await sbx.commands.run("echo awake")).stdout);
}
{
await using sbx = await Sandbox.create({ idle: null });
console.log((await sbx.commands.run("echo never suspends")).stdout);
}
A silent cell can be suspended midway
Inactivity counts the bytes that cross the endpoint. A cell or a command
that prints nothing for max_idle_seconds can be suspended while it
runs. The SDK waits for the resume and reattaches, but if your tasks are
long and silent, raise max_idle_seconds or pass idle=None.
Reading a handle does not wake the sandbox
While it is suspended, reading a background command, a PTY or a watch
blocks until something resumes it (resume(), or any new call such as
commands.run with auto-resume). It does not consume the reconnection
budget.
IdlePolicy options¶
| Python | TypeScript | Default | What it does |
|---|---|---|---|
max_idle_seconds |
maxIdleSeconds |
300 | seconds without traffic before suspending (minimum 60) |
auto_resume |
autoResume |
True |
the next call resumes a suspended sandbox |
suspended_duration_seconds |
suspendedDurationSeconds |
timeout − max_idle_seconds |
how long it can stay suspended before ending; 0 ends it on suspend |
idle=None |
idle: null |
— | turns auto-suspend off |
no idle |
no idle |
300 s | turned off if it does not fit before the timeout (in pause mode, 60 s) |
Cost¶
- Suspended: pays no compute, only the snapshot storage.
- Each suspend + resume cycle writes and reads the whole snapshot: it costs the same as ≈ 140 s of compute.
- Running: the normal compute for the image size.
Prices and examples, with sources: Costs.
Errors and troubleshooting¶
| Python | TypeScript | When | What to do |
|---|---|---|---|
SandboxStateException |
SandboxStateError |
the sandbox is ending or in a transition that does not allow the operation | wait and retry, or create a new one |
SandboxNotFoundException |
SandboxNotFoundError |
the sandbox no longer exists (it reached its timeout, even when suspended) |
create a new one; for more than 8 h, Persistence (persist=) |
InvalidArgumentException |
InvalidArgumentError |
an explicit idle with max_idle_seconds greater than or equal to the timeout (or max_lifetime) |
lower max_idle_seconds, pass idle=None or do not pass idle |
RateLimitException |
RateLimitError |
more than 2 suspensions per second in the account | the SDK already limits the rate per process; space out the pauses |
Differences from E2B¶
- Suspended time counts toward the 8 h maximum lifetime: a paused sandbox also ends 8 h after it started.
pause()always saves memory: in the shim, E2B'spause(keep_memory=False)isUnimplementedError.- Besides
connect()(E2B's form), Rayito has an explicitresume().
See also¶
- Concepts: deadline, cap and idle
- Server-side deadline: pause when a deadline expires
- Pool: suspended sandboxes ready to use
- Costs