Every run gets its own sandbox: a small Linux VM with a copy of the repo and nothing else. It starts in under a second, has no outbound network unless you allow it, and is wiped when the run ends. You can also make sandboxes yourself, to try a command, reproduce a bug, or prepare a fixture before a run.
Sandbox
A rack of sealed cells, one task in each: the repo is copied into one cell, an agent works inside it, its request to the network stops at the wall, and only the diff it wrote leaves through the out port before the cell is wiped clean.
0.5 ? 1.05 : 0.72) + rr(C, s + 2.0, s + 1.0 + e.y) * (e.z > 0.5 ? 0.62 : 0.42);
}
float dline(float C, float g, vec2 d, float upto) {
float txt = rr(C, g + 2.0, min(g + 1.0 + d.y, g + 1.0 + upto));
return eq(C, g) * (d.x > 0.5 ? 1.0 : 0.45) + txt * (d.x > 0.5 ? 0.72 : 0.4);
}
float walls(float C, float R, float c0, float c1, float r0, float r1, float pr, float oin, float oout) {
float port = rr(R, pr - 1.0, pr + 1.0);
float wall = (eq(C, c0) + eq(C, c1)) * rr(R, r0, r1) + (eq(R, r0) + eq(R, r1)) * rr(C, c0 + 1.0, c1 - 1.0);
wall *= 1.0 - port * (eq(C, c0) * oin + eq(C, c1) * oout);
float inner = (eq(C, c0 + 1.0) + eq(C, c1 - 1.0)) * rr(R, r0 + 1.0, r1 - 1.0);
inner *= 1.0 - port * (eq(C, c0 + 1.0) * oin + eq(C, c1 - 1.0) * oout);
float liner = (eq(R, r0 + 1.0) + eq(R, r1 - 1.0)) * rr(C, c0 + 2.0, c1 - 2.0);
float collar = (rr(C, c0 - 2.0, c0 - 1.0) + rr(C, c1 + 1.0, c1 + 2.0)) * (eq(R, pr - 2.0) + eq(R, pr + 2.0));
float bolt = (eq(C, c0 - 1.0) + eq(C, c1 + 1.0)) * rr(R, r0, r1) * eq(mod(R - r0, 4.0), 2.0) * step(2.5, abs(R - pr));
bolt += (eq(R, r0 - 1.0) + eq(R, r1 + 1.0)) * rr(C, c0, c1) * eq(mod(C - c0, 5.0), 2.0);
float room = rr(C, c0 + 2.0, c1 - 2.0) * rr(R, r0 + 2.0, r1 - 2.0);
return 1.05 * wall + 0.62 * inner + 0.4 * liner + 0.62 * collar + 0.5 * bolt + room * (0.13 + 0.12 * step(0.9, hash(vec2(C, R) + 7.1)));
}
float agent(vec2 p, vec2 c, float R0, float on, float work, float ang, float ph, float fi) {
float d = length(p - c);
float churn = noise(p * 0.1 + vec2(3.0 * fi + 1.3 * cos(ph), 2.0 * fi + 1.3 * sin(ph)));
float v = blob(p, c, R0 * 0.6, on * (1.3 + 0.2 * work + 0.4 * (churn - 0.5)));
v += on * 0.45 * exp(-pow(d - R0, 2.0) / 30.0);
v += on * 0.12 * step(d, R0 - 4.0);
v += work * blob(p, c + R0 * vec2(cos(ang), sin(ang)), 6.0, 1.2);
return v;
}
float sibling(vec2 p, float C, float R, float r0, float fi, float ang, float lines, float ph) {
float v = walls(C, R, 30.0, 70.0, r0, r0 + 8.0, r0 + 4.0, 0.0, 0.0);
float i = R - (r0 + 2.0);
if (i > -0.5 && i < 4.5) v += entry(C, 34.0 + treeRow(i).x, treeRow(i)) + eq(C, 33.0) * 0.26;
float k = R - (r0 + 3.0);
if (k > -0.5 && k < lines - 0.5) v += dline(C, 58.0, diffRow(k), 9.0);
v += agent(p, vec2(X(52.0), Y(r0 + 4.0)), 15.0, 1.0, 1.0, ang, ph, fi);
return 0.5 * v;
}
float field(vec2 p, float t) {
if (p.x < 18.0 || p.x > 702.0 || p.y < 18.0 || p.y > 442.0) return 0.0;
float C = floor((p.x - 4.0) / 7.0 + 0.5);
float R = floor((p.y - 3.5) / 9.4 + 0.5);
float ph = TAU * t / T;
float tau = mod(t + T0, T);
float v = 0.0;
float oin = sm(0.0, 0.08, tau) * (1.0 - sm(0.98, 1.06, tau));
float oout = sm(3.10, 3.18, tau) * (1.0 - sm(3.42, 3.50, tau));
float wx = tau < 3.55 ? -10.0 : mix(214.0, 500.0, ease((tau - 3.55) / 0.45));
float keep = 1.0 - step(p.x, wx);
v += walls(C, R, 30.0, 70.0, 16.0, 33.0, 25.0, oin, oout);
v += sibling(p, C, R, 4.0, 1.0, ph + 0.8, 2.0, ph);
v += sibling(p, C, R, 37.0, 2.0, ph + 3.4, 0.0, ph);
float hit = sm(2.19, 2.22, tau) * (1.0 - sm(2.24, 2.55, tau));
v += 0.45 * hit * eq(C, 70.0) * rr(R, 17.0, 21.0) * exp(-pow(R - 19.0, 2.0) / 3.0);
v += 0.3 * (eq(R, 24.0) + eq(R, 26.0)) * (rr(C, 21.0, 27.0) + rr(C, 73.0, 75.0));
{
float i = R - 19.0;
if (i > -0.5 && i < 11.5) {
vec3 e = treeRow(i);
float dep = 0.10 + 0.1 * floor(i / 2.0);
float lit = sm(dep, dep + 0.04, tau) * (1.0 - sm(dep + 0.12, dep + 0.28, tau));
v += entry(C, 9.0 + e.x, e) * (1.0 + 0.6 * lit) + eq(C, 8.0) * 0.26;
}
}
float crossed = 0.0;
for (int j = 0; j < 6; j++) {
float dep = 0.10 + 0.1 * float(j);
float u = (tau - dep) / 0.36;
crossed += step(dep + 0.2, tau);
if (u > 0.0 && u < 1.0) {
vec2 A = vec2(146.0, 238.5), B = vec2(228.0, 238.5), D = vec2(300.0, Y(19.5 + 2.0 * float(j)));
float l1 = length(B - A), l2 = length(D - B);
float s = u * (l1 + l2);
vec2 c = s < l1 ? mix(A, B, s / l1) : mix(B, D, (s - l1) / l2);
v += blob(p, c, 6.5, 1.15 * sm(0.0, 0.08, u) * (1.0 - sm(0.88, 1.0, u)));
}
}
{
float i = R - 19.0;
if (i > -0.5 && i < 11.5) {
vec3 e = treeRow(i);
float j = floor(i / 2.0);
float arr = 0.46 + 0.1 * j;
float rd = 1.18 + 0.1333 * j;
float lit = sm(rd, rd + 0.03, tau) * (1.0 - sm(rd + 0.12, rd + 0.2, tau));
v += keep * sm(arr - 0.03, arr + 0.03, tau) * (entry(C, 34.0 + e.x, e) * (1.0 + 0.6 * lit) + eq(C, 33.0) * 0.26);
}
}
vec2 core = vec2(X(52.0), Y(25.0));
float on = sm(0.98, 1.18, tau);
float work = sm(1.18, 1.26, tau) * (1.0 - sm(2.98, 3.08, tau));
v += keep * agent(p, core, 24.0, on, work, TAU * 3.0 * (tau - 1.18) / 1.9, ph, 0.0);
if (tau > 1.18 && tau < 1.98) {
float j = floor((tau - 1.18) / 0.1333);
v += seg(p, core, vec2(X(45.0), Y(19.5 + 2.0 * j)), 2.8, 0.42);
}
if (tau > 2.46 && tau < 3.06) {
float i = floor((tau - 2.46) / 0.1);
v += seg(p, core, vec2(X(59.0), Y(21.0 + i)), 2.8, 0.42);
}
{
vec2 A = vec2(388.0, 226.0), W = vec2(476.0, 182.1);
vec2 back = normalize(A - W);
float k = 1.15 * sm(1.98, 2.03, tau) * (1.0 - sm(2.28, 2.42, tau));
if (k > 0.0) {
vec2 c = tau < 2.2 ? mix(A, W, ease((tau - 1.98) / 0.22)) : W + back * 20.0 * eout((tau - 2.2) / 0.22);
float sq = exp(-pow((tau - 2.2) / 0.05, 2.0));
vec2 d = (p - c) / vec2(9.0 * (1.0 - 0.45 * sq), 9.0 * (1.0 + 0.6 * sq));
v += k * exp(-dot(d, d));
}
float sy = 10.0 + 60.0 * max(tau - 2.2, 0.0);
v += 0.8 * hit * step(p.x, 488.0) * exp(-pow(p.x - 482.0, 2.0) / 18.0 - pow(p.y - 182.1, 2.0) / (sy * sy));
}
{
float g = sm(3.05, 3.2, tau);
float e = ease(g);
for (int k = 0; k < 6; k++) {
float i = float(k);
float rowR = floor(mix(21.0 + i, 25.0, e) + 0.5);
float wr = clamp((tau - 2.46 - 0.1 * i) / 0.08, 0.0, 1.0);
if (wr > 0.0 && eq(R, rowR) > 0.5) v += keep * (1.0 - sm(0.6, 1.0, g)) * dline(C - floor(3.0 * e + 0.5), 58.0, diffRow(i), 8.0 * wr);
}
float u = (tau - 3.2) / 0.22;
v += blob(p, vec2(462.0, 238.5), 9.0, 1.3 * sm(3.12, 3.2, tau) * step(tau, 3.2));
if (u > 0.0 && u < 1.0) {
vec2 A = vec2(462.0, 238.5), B = vec2(518.0, 238.5), D = vec2(572.0, 252.0);
float l1 = length(B - A), l2 = length(D - B);
float s = ease(u) * (l1 + l2);
vec2 c = s < l1 ? mix(A, B, s / l1) : mix(B, D, (s - l1) / l2);
v += blob(p, c, 9.0, 1.3 * (1.0 - sm(0.85, 1.0, u)));
}
}
{
float i = R - 24.0;
if (i > -0.5 && i < 5.5) {
float a0 = 3.38 + 0.015 * i;
float hand = ease((tau - 4.02) / 0.28);
float vis = sm(a0, a0 + 0.04, tau) * (1.0 - sm(4.06, 4.3, tau));
v += vis * dline(C - floor(3.0 * hand + 0.5), 79.0, diffRow(i), 9.0);
}
}
if (tau > 3.55 && tau < 4.0) {
float wv = sm(3.55, 3.6, tau) * (1.0 - sm(3.94, 4.0, tau));
v += wv * rr(R, 18.0, 31.0) * (1.05 * exp(-pow(p.x - wx, 2.0) / 28.0) + 0.22 * step(p.x, wx) * step(wx - 40.0, p.x) * step(0.6, hash(vec2(C, R) + 2.3)));
}
{
float clr = tau < 0.12 ? 1.0 - sm(0.0, 0.12, tau) : 0.0;
float hIn = tau < 0.12 ? 6.0 * clr : crossed;
float nNet = tau < 0.12 ? clr : step(2.22, tau);
float hOut = tau < 0.12 ? clr : step(3.29, tau);
float base = 43.0;
float slots = (rr(C, 79.0, 80.0) + rr(C, 85.0, 86.0) + rr(C, 91.0, 92.0)) * rr(R, 37.0, base) * 0.13;
float inBar = rr(C, 79.0, 80.0) * rr(R, base - hIn + 1.0, base) * step(0.5, hIn) * (eq(R, base - floor(hIn + 0.5) + 1.0) > 0.5 ? 1.05 : 0.62);
float outBar = rr(C, 91.0, 92.0) * eq(R, base) * hOut * 1.05;
float netMark = rr(C, 85.0, 86.0) * eq(R, base) * nNet * 0.5;
v += max(slots, max(inBar, max(outBar, netMark)));
}
float near = 0.0;
v += blob(p, vec2(566.0, 102.0), 6.0, 1.0 + 0.12 * sin(ph + 0.0)); near = max(near, exp(-dot(p - vec2(566.0, 102.0), p - vec2(566.0, 102.0)) / 420.0));
v += blob(p, vec2(600.0, 88.0), 6.0, 1.0 + 0.12 * sin(ph + 1.3)); near = max(near, exp(-dot(p - vec2(600.0, 88.0), p - vec2(600.0, 88.0)) / 420.0));
v += blob(p, vec2(636.0, 100.0), 6.0, 1.0 + 0.12 * sin(ph + 2.6)); near = max(near, exp(-dot(p - vec2(636.0, 100.0), p - vec2(636.0, 100.0)) / 420.0));
v += blob(p, vec2(584.0, 128.0), 6.0, 1.0 + 0.12 * sin(ph + 3.9)); near = max(near, exp(-dot(p - vec2(584.0, 128.0), p - vec2(584.0, 128.0)) / 420.0));
v += blob(p, vec2(620.0, 128.0), 6.0, 1.0 + 0.12 * sin(ph + 5.2)); near = max(near, exp(-dot(p - vec2(620.0, 128.0), p - vec2(620.0, 128.0)) / 420.0));
v += blob(p, vec2(648.0, 118.0), 6.0, 1.0 + 0.12 * sin(ph + 6.5)); near = max(near, exp(-dot(p - vec2(648.0, 118.0), p - vec2(648.0, 118.0)) / 420.0));
float ln = 0.0;
ln = max(ln, seg(p, vec2(566.0, 102.0), vec2(600.0, 88.0), 3.4, 0.3));
ln = max(ln, seg(p, vec2(600.0, 88.0), vec2(636.0, 100.0), 3.4, 0.3));
ln = max(ln, seg(p, vec2(566.0, 102.0), vec2(584.0, 128.0), 3.4, 0.3));
ln = max(ln, seg(p, vec2(584.0, 128.0), vec2(620.0, 128.0), 3.4, 0.3));
ln = max(ln, seg(p, vec2(600.0, 88.0), vec2(620.0, 128.0), 3.4, 0.3));
ln = max(ln, seg(p, vec2(636.0, 100.0), vec2(648.0, 118.0), 3.4, 0.3));
ln = max(ln, seg(p, vec2(620.0, 128.0), vec2(648.0, 118.0), 3.4, 0.3));
v += ln;
float hs = hash(vec2(C, R) + 3.7);
v += near * step(0.78, hs) * (0.24 + 0.1 * sin(ph + hs * 40.0));
float ne = length((p - vec2(607.0, 110.0)) / vec2(56.0, 36.0));
v += (1.0 - sm(0.82, 1.0, ne)) * step(0.5, hs) * 0.19 + 0.3 * exp(-pow(ne - 1.0, 2.0) / 0.006) * step(0.35, hs);
{
vec2 A = vec2(510.0, 176.0), B = vec2(578.0, 132.0);
vec2 ab = B - A;
float h = clamp(dot(p - A, ab) / dot(ab, ab), 0.0, 1.0);
vec2 d = (p - A - ab * h) / vec2(3.6, 4.8);
v += 0.32 * (1.0 - sm(0.8, 1.05, length(d))) * step(0.5, fract(h * length(ab) / 16.0));
}
return v;
}]]>
One sealed cell per task. The repo is copied in, the agent works inside, its call to the network stops at the wall, and only the diff leaves before the cell is wiped. Note A sandbox runs for up to 24 hours on Team and 1 hour on Hobby. For longer work, pause it : pausing resets the clock, and its disk and memory are kept until you resume or kill it.
A sandbox stays up until its timeout runs out. The timeout is in milliseconds and counts from the last command you ran, so a busy sandbox is never cut off mid-task.
import { Kestrel } from "@kestrel/sdk" ;
const kestrel = new Kestrel ();
const sandbox = await kestrel.sandboxes. create ({
repo: "acme/web-app" ,
timeoutMs: 600_000 , // 10 minutes after the last command
});
console. log (sandbox.id, sandbox.state); // "sbx_41c9", "running" from kestrel import Kestrel
kestrel = Kestrel()
sandbox = kestrel.sandboxes.create(
repo = "acme/web-app" ,
timeout_ms = 600_000 , # 10 minutes after the last command
)
print (sandbox.id, sandbox.state) # "sbx_41c9", "running"
const result = await sandbox.commands. run ( "npm test -- checkout" );
console. log (result.exitCode, result.stdout); result = sandbox.commands.run( "npm test -- checkout" )
print (result.exit_code, result.stdout)
State What it means Billed startingThe VM boots and the repo is copied in. Usually under a second. No runningCommands run. The timeout counts down between them. Sandbox minutes pausedDisk and memory are saved and the VM is stopped. Storage only killedThe VM and its disk are gone. This can't be undone. No
await sandbox. setTimeout ( 30 * 60_000 ); // 30 more minutes from now sandbox.set_timeout( 30 * 60_000 ) # 30 more minutes from now
Killing is immediate. Anything not downloaded or snapshotted is lost.