What's new in 0.6.0¶
Published on 2026-10-03: Python SDK rayito 0.6.0, TypeScript SDK
rayito 0.6.0 and rayd 0.6.0.
0.6.0 brings six new features and a single convention for deploying their infrastructure in your account. All of them are off by default: without the corresponding option, a sandbox behaves byte for byte as in 0.5.x and the SDK makes no new call to AWS (a golden trace in each SDK's tests checks it).
Upgrade to 0.6.1
0.6.1 fixes that redeploying an optional stack sent its parameters back to
their defaults and that reincarnate() lost mounts=, events= and
telemetry=: What's new in 0.6.1.
At a glance¶
| Feature | Python | TypeScript | Image | Idle cost | Guide |
|---|---|---|---|---|---|
| S3 mounts | mounts={"/mnt/x": S3Mount(...)} |
mounts: { "/mnt/x": new S3Mount({...}) } |
rayito-base-caps, rayd 0.6.0 |
$0 (IAM only) | S3 mounts |
| Sizes | size="4gb" |
size: "4gb" |
one image per size (rayito image publish --sizes) |
snapshot per published image | Sizes |
| Events and webhooks | events=LifecycleEvents() |
events: new LifecycleEvents() |
any with rayd 0.6.0 |
≈ $0.40/month (events-webhooks stack) |
Events and webhooks |
| OTLP export | telemetry=TelemetryExport(...) |
telemetry: new TelemetryExport({...}) |
rayd 0.6.0; caps with OtlpAuth.execution_role() |
$0 (IAM only) | OTLP export |
| Declarative templates | Template.build(t, "name", bucket=...) |
Template.build(t, "name", { bucket }) |
composed on top of a published rayito-base |
snapshot per built version | Templates |
| Secret gateway | gateways={"api": SecretGateway(...)} |
gateways: { api: new SecretGateway({...}) } |
any with rayd 0.6.0 |
$0 (Secrets Manager reads by usage) | Secret gateway |
| Optional stacks | OptionalStacks().deploy(...) |
new OptionalStacks().deploy(...) |
— | that of each component | Optional stacks |
All accepted against real AWS; each guide cites its measurements in
AWS_API_NOTES.md. The figures and permissions of each, in a single table:
Optional features and their cost.
S3 mounts¶
A bucket (or a prefix) as a normal folder in the sandbox, with Mountpoint for
Amazon S3. create() does not return the sandbox until each mount is
mounted; if any fails, it terminates it and raises MountException
(TypeScript: MountError) with a closed code.
from rayito import S3Mount, Sandbox
with Sandbox.create(
"rayito-base-caps",
execution_role_arn="arn:aws:iam::123456789012:role/rayito-sandbox-caps",
mounts={"/mnt/data": S3Mount(bucket="amzn-s3-demo-bucket", prefix="team7/")},
) as sbx:
print(sbx.commands.run("ls /mnt/data").stdout)
print(sbx.mounts["/mnt/data"].state) # "mounted"
import { S3Mount, Sandbox } from "rayito";
await using sbx = await Sandbox.create({
template: "rayito-base-caps",
executionRoleArn: "arn:aws:iam::123456789012:role/rayito-sandbox-caps",
mounts: { "/mnt/data": new S3Mount({ bucket: "amzn-s3-demo-bucket", prefix: "team7/" }) },
});
console.log((await sbx.commands.run("ls /mnt/data")).stdout);
console.log((await sbx.mounts()).get("/mnt/data")?.state); // "mounted"
It needs the s3-mounts stack's policy on the execution role and the bucket
in the image's allowlist (RAYITO_ALLOWED_MOUNT_BUCKETS).
Size catalog¶
Five named sizes (512mb, 1gb, 2gb, 4gb, 8gb) that the SDK resolves
on the client, with no RPC: size="4gb" launches the rayito-base-4gb
image. An intermediate size is rounded up with a warning.
rayito image publish --sizes 512mb,4gb publishes the images of each size
from the same artifact.
Lifecycle events and webhooks¶
rayd signs each transition (created, paused, resumed, killed) and a
stack in your account stores them in DynamoDB and delivers them to your
webhooks with E2B's signature: a receiver written for E2B verifies them
unchanged (how to verify the signature).
from rayito import LifecycleEvents, Sandbox
events = LifecycleEvents() # the `rayito events deploy` stack
with Sandbox.create(
execution_role_arn="arn:aws:iam::123456789012:role/rayito-sandbox",
logging="cloudwatch",
events=events,
) as sbx:
sbx.pause()
sbx.resume()
for event in events.get_events(sandbox_id=sbx.sandbox_id, order="asc"):
print(event.type) # sandbox.lifecycle.created, .paused, .resumed, .killed
import { LifecycleEvents, Sandbox } from "rayito";
const events = new LifecycleEvents(); // the `rayito events deploy` stack
const sbx = await Sandbox.create({
executionRoleArn: "arn:aws:iam::123456789012:role/rayito-sandbox",
logging: "cloudwatch",
events,
});
await sbx.pause();
await sbx.resume();
await sbx.kill();
for (const event of await events.getEvents({ sandboxId: sbx.sandboxId, order: "asc" })) {
console.log(event.kind); // created, paused, resumed, killed
}
OTLP export from rayd¶
rayd exports 7 CPU, memory and disk metrics to CloudWatch over OTLP/HTTP
every interval_s (60 s by default), signed with SigV4 (the execution role)
or with a token stored in Secrets Manager. About $0.00002 per sandbox-hour.
import { OtlpAuth, Sandbox, TelemetryExport } from "rayito";
await using sbx = await Sandbox.create({
template: "rayito-base-caps",
executionRoleArn: "arn:aws:iam::123456789012:role/rayito-sandbox-caps",
telemetry: new TelemetryExport({ auth: OtlpAuth.executionRole() }),
});
console.log((await sbx.getTelemetryStatus()).exported);
Declarative templates¶
E2B v2's Template DSL, compiled in your process to a deterministic
Dockerfile on top of a published rayito-base image, and built with Lambda
MicroVMs. No control plane of its own. In the shim, Template and
AsyncTemplate really build with E2B's signature.
from rayito import Sandbox, Template, wait_for_port
t = (
Template()
.from_base_image("rayito-base")
.pip_install(["pandas==2.2.3"])
.set_start_cmd("python3 -m http.server 8000", wait_for_port(8000))
)
info = Template.build(t, "my-api", bucket="amzn-s3-demo-bucket")
with Sandbox.create(info.template_id) as sbx:
print(sbx.commands.run("curl -s localhost:8000").exit_code)
import { Sandbox, Template, waitForPort } from "rayito";
const t = new Template()
.fromBaseImage("rayito-base")
.pipInstall(["pandas==2.2.3"])
.setStartCmd("python3 -m http.server 8000", waitForPort(8000));
const info = await Template.build(t, "my-api", { bucket: "amzn-s3-demo-bucket" });
await using sbx = await Sandbox.create({ template: info.templateId });
console.log((await sbx.commands.run("curl -s localhost:8000")).exitCode);
Secret gateway¶
The sandbox's code uses a credential without being able to read it:
rayd opens a loopback gateway that checks a method and path allowlist,
injects the real header (read from Secrets Manager) and forwards to the
upstream you declared. sbx.gateways.refresh() rotates the secret without
recreating the sandbox.
from rayito import Sandbox, SecretGateway
with Sandbox.create(
gateways={
"anthropic": SecretGateway(
upstream="https://api.anthropic.com",
headers={"x-api-key": "anthropic"}, # secret name, not its value
allow=[("POST", "/v1/messages")],
)
}
) as sbx:
url = sbx.gateways["anthropic"].url # http://127.0.0.1:<port>
sbx.commands.run("python agent.py", envs={"ANTHROPIC_BASE_URL": url})
import { Sandbox, SecretGateway } from "rayito";
await using sbx = await Sandbox.create({
gateways: {
anthropic: new SecretGateway({
upstream: "https://api.anthropic.com",
headers: { "x-api-key": "anthropic" }, // secret name, not its value
allow: [["POST", "/v1/messages"]],
}),
},
});
const url = sbx.gateways.get("anthropic")?.url ?? "";
await sbx.commands.run("python agent.py", { envs: { ANTHROPIC_BASE_URL: url } });
Optional stacks and rayito stack¶
Each feature with its own infrastructure is an OptionalStack component: a
CloudFormation template that you deploy yourself, with
OptionalStacks.deploy/status/destroy or rayito stack. deploy always
prints the cost and asks for confirmation; nothing deploys itself.
Under the hood: ConfigureSandbox¶
The features that live inside the sandbox (mounts, events, telemetry and
gateways) reach rayd through a single new channel, ConfigureSandbox,
right after the first Health and before create() returns the sandbox. If a
section fails, create() terminates the MicroVM it just launched (unless
keep_on_failure=True) and rethrows the error: you never receive a
half-configured sandbox. Against an image with a rayd earlier than 0.6.0,
those options fail with UnimplementedError. Design: ADR-015 and ADR-016 in
ARCHITECTURE.md.
How to upgrade from 0.5.x¶
- Upgrade the SDK (
pip install -U "rayito[cli]"orpnpm add rayito@latest). Without new options, your code stays the same. - To use
mounts,events,telemetry,gatewaysor the templates'set_start_cmd(), republish your image onrayd0.6.x (Images) and check compatibility withrayito doctor. - Deploy only the stacks of the features you use
(
rayito stack deploy <component>).
There are no incompatible changes in the public API from 0.5.x. The only
thing that changes for the E2B shim: Template, AsyncTemplate,
TemplateException and BuildException go from raising UnimplementedError
to really working (E2B parity).
What 0.6.0 does not bring¶
EFS volumes (volumes=) and custom domain (domain=) were still in
development: their options existed in create()'s signature and raised
UnimplementedError without calling AWS. EFS volumes and CustomDomain
(experimental) arrive in 0.7.0; domain= remains
in development.
Full details: Changelog.