Changelog
What shipped in each v0.0.x release of the gateway, newest first. The gateway reports its version at GET /healthz. Source: CHANGELOG.md.
0.0.25 (2026-10-19)
Changed
- Internal simplification of the remediation relay,
sbx doctorand their tests; no change in behavior (#1264).
0.0.24 (2026-10-19)
Fixed
- The served
/openapi.jsonandllms-full.txtcarry no internal provenance or identities: owner-decision notes, an internal lifecycle tag name, lane names, a source path and an internal user@host example are gone; a test pins the vocabulary (#1260).
0.0.23 (2026-10-19)
Added
- Error bodies: every 401/402/403/404/429 from the control plane carries a
hint(the next move) and adocs_url, including create-rate 429s and refusals relayed from the upstream;sbx doctorchecks key, quota, credit, template and GPU access in one command (#939, #1247).
Fixed
- Public port routes (
<port>-<id>.<domain>) accept request bodies up to 2g and stream them unbuffered; only Hosts the gateway routes to a port (ports 1..65535) get that cap, every other Host keeps the 512m control-plane cap (#1008, #1249). /v0/factsrejects a non-finite GPU class price instead of serving it (#1224, #1248).timeout_mindocuments the idle keep-alive it is (re-armed by each call; a value under the 300 s idle floor waits the floor), and a test pins the gateway's keep-alive renewal (#998, #1246).
0.0.22 (2026-10-19)
Fixed
- GPU billing: worker init is no longer billed, and a TAIL_ONLY verdict bills its tail only after an exec ran, not after a fresh resume (#1236). A live
none-mode lease settles its residency at most once a minute instead of every 15 s reap tick, and every credit read counts the unsettled interval, so gates refuse on settled + open spend (#1057, #1244). - An upstream 402 is a retryable 503
platform_capacity, not a tenant credit 402; its logged body is redacted (#1237). - A lone re-pause 404 no longer marks a fork source dead; only an exact
pausedprobe state confirms a lost re-pause (#593, #1229, #1238). - Templates:
/initadds127.0.1.1 <hostname>to/etc/hosts(remounting the read-only bind) sosudostops warning (#872, #1243); a not-found<alias>:<size>ref names the sizes the alias offers (#1245). - The served OpenAPI spec carries no internal operations detail (#1231).
- prod serves GPU prices from
/v0/facts(the price sheet path points at the deployed site) (#1232). - Docs: the CLI sections install the public kit CLI (#1239); MCP pages name all ten tools, the paused quota names the free tier (10), and the GPU ceiling points at the live
max_admit_gib(#1251).
Tests
- An unknown
accel.templateon the front is a 400 listing the names (#591, #1234); the backgrounded-child exec test no longer sits on the wall-clock edge (#1241).
Fixed
- Gateway: a 402 arriving from the sandbox backend is no longer relayed to the tenant as a credit-402. The gateway enforces each tenant's own credit before any upstream call, so an upstream 402 can only be the platform's upstream-key budget (on 2026-10-09 it hit its 200 h pool and every tenant got
credit exhausted: running-hours budget used upwith hours left on its own key). It now answers a retryable503platform_capacitywithRetry-Afterand anerror_id; the upstream's wording stays in the server log (#506).
0.0.21 (2026-10-18)
Added
- A public, unauthenticated
GET /v0/facts: the user-facing numbers the docs quote (upload cap per file, idle-pause seconds, paused retention days, GPU leases per owner per node, GPU VRAM slice bounds, the docs-sourced price sheet), each read at request time from the constant, env setting orConfigthe gateway enforces. The GPU VRAM ceiling is now one constant,MAX_SLICE_GIB = 32.0, at every enforcement site (#1200).
Fixed
/v0/factsserves the live memory price ($0.003 per configured GiB-hour) and the priced 5090 class; a merge-order collision had left it quoting the retired $0.006 (#1226).
Changed
- CI: a pull request whose only repo-gate inputs are top-level
tests/test_*.pyfiles runs just those files in therepojob; PRs skip the duplicate suite replay inside the public cut, and the repo pytest step runs on 4 xdist workers. Pushes to main still run everything (#1218).
Tests
- GPU node-mode
accel.templateis pinned end to end: create wire, GET and list (#1221).
0.0.20 (2026-10-18)
Added
- GPU: an operator host-memory admission floor,
GATEWAY_GPU_ADMIT_FLOOR_GB(default 0, off). A create is refused with the typed retryable capacity refusal while host memory, after every reservation in flight, would drop below the floor; creates and every spill (pause, idle auto-pause, reaper, eviction) share one reservation ledger, and capacity reports the same bound (#1216). - SLA prober: a deterministic triage comment on every newly created alert issue (failing checks with recent samples, every service's state, the reported build, runbook links), posted after the alert and the state save under its own deadline (#1214). The GPU sandbox is probed on its own cron trigger and shares one alert ladder with the probe run; a host dead-man timer watches the status document (#1186, #1192, #1193).
Changed
- GPU price v6 on
/pricing/: every class's list price is now derived from the hourly rent of the card we resell —rent / (VRAM × 50% target busy utilization × 60%), rounded up to the cent, break-even at about 30% busy — with rents read from the live Lambda and RunPod price pages on 2026-10-18 (ops/pricing/price_v6.pyis the derivation). The 5090 gets its first list price and stays the only live class. A new dedicated whole-card tier is listed per class: the whole card to one tenant, billed per running second including idle time, available on request. Prices still live only insite/pricing/gpu-classes.json(#1215). - Pricing: CPU sandbox memory is $0.003 per configured GiB-hour while running, down from $0.006 (#1212).
- GPU: the spill guard subtracts spills already in flight, so concurrent pauses cannot overrun host memory together (#1216).
Fixed
- sbx CLI: an empty
env.tomlsetup takes no snapshot and the bundle is uploaded once (#1206); the GitHub slug is read from ssh-over-443 remotes (#1207).
Documentation
- GPU guide: the CLI and MCP paths next to REST (#1189); how to check the
mujocostack by import and run Blender headless in theblenderGPU template (#1213); Source references point at the current code (#1208).
0.0.19 (2026-10-18)
Fixed
- MCP:
sandbox_createwithvram_gband notemplatecreates a GPU sandbox on the default GPU template instead of failing withunknown GPU template None(#1185).
0.0.18 (2026-10-18)
Changed
- GPU bench (
bench/gpu_small): reads uploads where the product puts them, leaves the reservedxalone, records overhead as n/a on a client without nvidia-smi, records the status and body of every failed exec, keeps going after a failed lifecycle step or a case that raises, and reports cold-start latency from the iterations that completed (#1162).
Fixed
- GPU plane: a create whose slice no card can seat even empty (for example tier XL, 32 GiB, on today's RTX 5090) is refused at once with 400
gpu_slice_too_largenaming the largest slice that seats, instead of parking every idle lease on the node and answering a retryable 503. The limit is the card's physical memory, so temporary VRAM pressure stays a retryable refusal. On a node fleet a node's 400 sends the create on to the next node; when every node it was offered to said its card cannot seat the slice, the front answers the retryable 409gpu_capacity_exhaustednaming the largest slice an answering node seats, never the 400 (#1163). - GPU billing: a status read of a node-fleet lease no longer charges the projected idle tail, which the node may still classify as idle (#1170).
- GPU plane: a create refused because the host has no memory to park an idle lease now logs which lease it could not park, and the GPU guide no longer says a node fleet answers resume contention with 409 (#1173).
- GPU plane: a key whose CPU hours are used up can still create GPU sandboxes while its GPU bucket lasts. The CPU credit check no longer gates a GPU create, and GPU exec and resume never did. The beta's fleet-wide GPU budget refusal is now a typed 503
gpu_global_budgetwithretry_after_s300 and aRetry-Afterheader (#1177). - MCP: a refused GPU
sandbox_create,sandbox_execorsandbox_killnow carries the REST refusal's recovery fields (error_code,param,retry_after_s,class,alternatives,eta_s) in the JSON-RPCerror.data. A create's lifetime no longer promises an idle auto-pause: whether an idle GPU sandbox frees its seat depends on its template and on the deployment, and the reply says to release it withsandbox_kill(#1179). - GPU plane: a
codeexec now returns what its child processes print (nvidia-smi,pip, a training script run throughsubprocess), in order with the job's own prints. A report under 4 MiB keeps both 64 KiB stream tails; when a report leaves no room for them, the reply says so instreams_cut(#1180). - MCP: the server instructions and the
sandbox_createdescription no longer promise that an idle GPU sandbox frees its seat after 600 s; they say that this depends on the template and on the deployment (#1182).
0.0.17 (2026-10-17)
Added
- Admin CLI:
python -m gateway.admin key create --name N --tier free|verifiedmints the same key a sign-in mints (tier quotas and credit, no one-time GPU grant); combining--tierwith a manual limit flag is refused (#1150).
Changed
- Pricing page: the free GPU allowance (1 GPU-hour a month) and the verified one (2 GPU-hours a month) are stated as live, no longer as rolling out (#1147).
- Docs (quickstart, limits, landing page,
llms-full.txt): a CPU sandbox becomes eligible to pause aftermax(timeout, 300 s)since its last activity, and the reaper may act later;endAt(MCPend_at) right after a create reflects the requested timeout only;autoPause: falsekills at the timeout without the floor (#1160).
Fixed
- GPU plane: a create that names an unknown
accel.templateon the node fleet now answers400 unknown accel.template '<name>'; available: <names>, listing the templates the fleet serves, the same shape as the single-node plane (#1149). - GPU plane: while card verification is closed (
GET /v1/tiersreportscard_verification_enabled: false), the free-tier refusal (403gpu_free_tier_limit) ends "Contact support to lift this limit" instead of inviting a card check; the site and docs say card verification is not open yet (#1154). - GPU docs and the MCP tool descriptions no longer offer a
kerneltemplate, state that slices above the card'smax_admit_gib(28.27 GiB today) are not seated, give tier S as 1 GiB, recompute the seat counts from the current card reading, and state the measured small-CUDA sizes per slice (#1155). - GPU billing: an idle stretch that follows an exec, a resume or a thaw no longer counts toward
meters.billed_sbefore the activity sampler has classified it (onlysuspend_mode: noneleases bill unclassified idle time). Before, a read during that stretch showed phantom billed seconds, and the next busy exec's seconds only replaced them (#1156). - GPU guide: quotes the egress refusal production actually returns and names
GET /gpu/templatesas the GPU template catalog (#1165). - GPU plane: a resume or exec of a parked lease that the node cannot swap back in for lack of host memory now answers the typed, retryable 503
gpu_capacity_exhaustedwithRetry-After(the refusal a create gets), instead of a flat 409 that named another tenant's lease (#1166). - GPU billing: charges that were lost are now billed: an exec interrupted by a gateway restart, background GPU compute on a lease re-adopted after a restart, and on the node fleet a usage row whose insert failed (it is retried on the next reply instead of being dropped) (#1169).
- GPU plane: a job that leaves a file named like the gateway's temporary job file in its workdir no longer makes every later exec on that lease fail with a 500; job files are published through unpredictable temporary names that never follow a planted link (#1168).
- GPU plane: a cancelled request no longer destroys another request's job, abandons a newly created worker or loses the final charge of a removed lease (settlement advances only after the durable write, and a captured final meter is retried). The create's requested lifetime now applies to every live phase, also through a node fleet; lease lists report the deadline, and the absolute expiry survives a gateway restart. An expired lease whose worker container is gone no longer stops later gateway boots, and an oversized integer
idle_timeout_sanswers 400 (#1161).
0.0.16 (2026-10-17)
Added
- Free-tier GPU trial (#1111): a GitHub sign-in with no card can run a small GPU trial on the cards we own.
GET /v1/tiersshows the free tier withgpu: true,gpu_hours_monthly: 1.0,gpu_leases: 1andgpu_max_vram_gb: 2.0. The trial is 1 GPU-hour per UTC month on the verified tier's own meter, one live lease (a paused one counts), a slice of at most 2 GiB (vram_gb, oraccel.tierS/M), noaccel.network: "internet", only the classes inTIER_FREE_GPU_CLASSES(defaultnvidia.5090; a create that names no class gets the first), the default template only, and an idle auto-pause of 60-600 s. A create beyond that answers 403gpu_free_tier_limitwhoseparamnames the field (accel.vram_gb,accel.network,accel.class,accel.templateoraccel.idle_timeout_s). MCPsandbox_creategives a free key 2 GiB when it names novram_gb.TIER_FREE_GPU=falseswitches the trial off, and theTIER_FREE_GPU*andTIER_VERIFIED_GPU*settings now survive a node re-render. Boot fails on a NaN, infinite or negative GPU-hours value, a free slice cap outside 0.5-32 GiB, a free class that is not an accel class, and an empty class list while the trial is on. - The GPU guide and the pause semantics page are published on the docs site at
/docs/gpu/and/docs/gpu/pause/, with Markdown twins at/docs/gpu/index.mdand/docs/gpu/pause/index.md(#1118). They are generated fromdocs/gpu.mdanddocs/gpu-pause.md, listed right after the docs hub inllms.txt, inlined inllms-full.txt, and linked from the docs hub, quickstart, templates, agents, limits and errors pages. - On-demand GPU classes in the node-fleet front: an
accel.classthat no node serves yet but that the operator rents on demand (GATEWAY_GPU_ON_DEMAND) answers the retryable 503gpu_class_provisioninginstead of 400gpu_class_unavailable, withRetry-Afterandeta_sas the boot estimate (the provisioner's own for the class when it publishes one, else the catalog'sboot_eta_s), and records the demand for the operator's provisioner (GATEWAY_GPU_DEMAND_FILE). When the provisioner reports no stock (GATEWAY_GPU_SUPPLY_FILE) the answer is 503gpu_class_no_supply(Retry-After: 300,alternativeslists the classes served now).accel.wait_swaits out a provisioning refusal like a full plane.GET /v0/gpu/capacitylists each rentable class that has no ready node as a row withstate: "on_demand"andboot_eta_s(#1134). GATEWAY_GPU_NODES_FILE: GPU nodes the provisioner rents join and leave the front's placement when the file changes, with no restart; the file applies whole or not at all (one bad entry keeps the last good view), onlyreadynodes take new leases,drainingnodes keep serving the leases they hold, and a node may carry its own token. The verified tier's GPU allowance and the free tier's GPU trial work on a front that only has a nodes file, as on one withGATEWAY_GPU_NODES(GET /v1/tiers, GPU create and capacity agree). Without any of the new settings nothing changes (#1134).
Changed
- A GPU node reports one class per card when two cards share a model name:
nvidia.a100-40gandnvidia.a100-80g(werenvidia.a100),nvidia.h100-sxmandnvidia.h100-pcie(werenvidia.h100), andnvidia.rtx6000for the Quadro RTX 6000 (wasnvidia.6000).nvidia.a100-80gis a supported variant the site does not advertise. Every other card is unchanged andGATEWAY_GPU_CLASSESstill overrides the query. Ask such a node for the new name (#1126). - A GPU node that does not answer is reported as
node unreachable: <error class>; the text of the underlying HTTP error is no longer part of the answer or the log. AGATEWAY_GPU_NODESURL whose port is not a number in 1-65535 is refused at start (#1134).
Fixed
- GPU
suspend_mode: "none"billing on a node fleet (#1129). The front now charges a none lease's whole residency as busy GPU time, as the node anddocs/gpu.mdsay; it charged only the exec seconds, so an idle none lease on a fleet was charged nothing. A job still running counts once, and a lease already open when the front is upgraded is charged the rest of its residency on its next reply. No price change. - The docs no longer say a GPU sandbox cannot pause or that GPU leases never have network: an idle GPU lease pauses on
accel.idle_timeout_s, andaccel.network: "internet"opens egress on nodes that enable it. The guide now states that a pause bills 0 only when the lease is spilled (on a hard-cap host, a lease whose other processes hold VRAM, or whose VRAM reading is unavailable, is frozen and its frozen time bills in full; a host without a hard cap always spills), that the lease cap of 8 is per GPU host or node while the tier cap (averifiedkey holds 1) is counted across a node fleet, where a dead lease leaves the count once its node lists it dead or no longer lists it, that a node fleet relays a node's exec refusal with the typed fields, wherebilled_sappears (themetersblock of the GET and exec replies, the top level of the DELETE reply), and the typed GPU error codes the errors page was missing (#1118). - The README, the signup runbook and the env example said
TIER_VERIFIED_GPUdefaults to false; the code has defaulted to true since #1045, and the docs now say so (#1111). - GPU template docs and API reference:
accel.reservedefaults toexecfor every template (#1130). The OpenAPI description andllms-full.txtcalledholdthe default (andexecthe default only for the mujoco, warp and jax templates), as did the blender and sam template docs; the gateway has defaulted toexecsince #901 and #1063, andholdis an explicit opt-in. TheGPU template imageworkflow also skips a template whose base image is built by hand (gpu-sam) instead of failing on the pull; that part is CI only. - GPU node responsiveness while a container starts or stops (#1131). The capacity probe (
GET /internal/gpu/capacity), the drain call, the operator lease list and the single-nodeGET /v0/gpu/capacityneed the lock that a launching create or a destroy holds. They waited for it on the event loop, so every other request of that process stalled until the launch ended. They now wait without a thread, for at most 5 s and with at most 128 requests waiting at once; past either limit they answer a retryable 503gpu_node_busywithRetry-After: 5and have applied nothing, and a fleet front reads that as the node not answering that probe. - The API's
llms.txt,llms-full.txtandopenapi.jsonno longer say that GPU leases never have network, that a key holds 8 leases, or that idle time always bills 0 (#1135). A lease has no network unless it is created withaccel.network: "internet"on a node that opted in (otherwise 400); the cap is 8 live leases per key on each GPU host or node, and averifiedkey holds 1 in total; idle time bills 0 except for asuspend_mode: "none"lease (its whole residency) and a pause that froze the lease (the frozen time); on a hard-cap host, a pause freezes instead of spilling when processes other than the worker hold VRAM; andDELETEanswers the final meter snapshot itself, withbilled_sat its top level, whilemeters.freezesis read fromGET /sandboxes/{id}. - The GPU guide's error table and the errors page no longer say that every
gpu_node_busyanswer carriesclass(#1135): theGET /v0/gpu/capacityanswer does; a node's lease list, capacity probe and drain call answererror,error_code,message,retry_after_sand theRetry-Afterheader only.
0.0.15 (2026-10-17)
Changed
- The
/pricing/page sells GPU by class instead of one flat rate (#1132). A table lists the nine classes with availability and the list price per busy GiB-hour, with the price of a 0.5 GiB and a 4 GiB slice per busy hour beside it. Onlynvidia.5090is live, and it has no list price yet (beta use draws down the GPU allowance); the other eight are on request at their list price. The calculator prices the class you pick and shows "No list price yet" for a class without one. The prices live insite/pricing/gpu-classes.json;site/tools/build_gpu_pricing.pycopies them into the page and the calculator, andsite-checkfails when they drift.
Fixed
- MCP
sandbox_execon a GPU sandbox no longer returns libvgpu's[HAMI-core Msg(and[HAMI-core Warn(lines instderr(#1117), the filter #1113 gave the RESTcmdexec. They are removed before the output cap applies, so they neither use it up nor setstderr_truncated;[HAMI-core ERRORlines and stdout are untouched. It applies to every lease once the front is deployed. - GPU node drain and class discovery (#1124). A drain now takes effect where a create claims its seat: a create that was still probing VRAM when the drain landed is refused and launches nothing, and
idle_sincestaysnullwhile a create is in flight, so an autoscaler no longer releases a node that is about to take a lease. A direct create on a draining node answers 503gpu_node_drainingwithRetry-After: 30, as documented (it answered 409, which a front reads as "node full"). The front reads that 503 as draining and tries the next node; when no active node takes the create and a draining node had room, it answers the same 503 instead of the fleet-full 409gpu_capacity_exhausted. A node whose capacity probe lists no accel class (its GPU query failed, or an older build) is no longer read as annvidia.5090: it takes no lease, with or withoutaccel.class. When no node with a known class can take the create, the answer is a retryable 503gpu_node_unreachable(Retry-After: 30), also for an internet create that only such a node could serve (not the egress 400); known-class nodes that are all full still answer the fleet-full 409gpu_capacity_exhausted. - GPU lease cap on a node fleet (#1127). A lease that a node lists
dead, or no longer lists, stops counting against a key's tier lease cap: before a create is refused at the cap, the front asks the nodes holding the key's leases, so a key is no longer refused for a lease it cannot use. A lease on a node that cannot be read, or that answers with something other than a lease list, keeps counting until the node answers properly, and two creates racing at the cap still admit only one. - GPU exec refusals on a node fleet (#1128). A fair-share 429, a capacity 503, a worker boot failure and a draining node now reach the caller with the typed body the single-node plane gives (
error,error_code,message,retry_after_sand theRetry-Afterheader, plusclasswhen the lease's card is known). The front relayed only{code, message}: a 429 carried noerror_code, and a 503 was reported asgpu_node_unreachable. A 429 from a node that predates the typed body is still read asgpu_fair_share.
0.0.14 (2026-10-17)
Added
- GPU capacity view (#1040):
GET /v0/gpu/capacitylists, per card class, the seats and VRAM free and total and the templates on offer, plus the caller's remaining credit, running leases and lease cap. Capacity, lease-cap, credit and fair-share refusals carry a stableerrorcode (gpu_capacity_exhausted,gpu_lease_cap,gpu_credit_exhausted,gpu_fair_share) and, where a retry helps,retry_after_sandRetry-After. - GPU create options under
accel:tier(S/M/L/XL= 1/2/8/32 GiB; S is 1 GiB because a 0.5 GiB slice cannot back a first CUDA tensor allocation) names the slice (#1083, #1085);wait_s(0-120 s, default 0) waits for a seat instead of refusing at once (#1058);idle_timeout_s(0 = off, else 60-86400 s; default 600 s, off forsuspend_mode: "none") pauses a lease that went idle (#1043);network: "internet"gives a lease outbound internet on nodes that setGATEWAY_GPU_EGRESS=1(default off; private, loopback and metadata ranges stay blocked) (#994). - GPU
cmdexec (#1015):POST /sandboxes/{id}/exectakes a shellcmdas an alternative tocode(exactly one of the two); a non-zero exit is stillok: true, withexit_codein the reply. - GPU usage block (#1039):
GET /sandboxes/{id}on a GPU lease returns agpuobject (class, device name, VRAM slice, VRAM used and peak, utilization,sampled_at); a value that is not measured isnull. - GPU lease uploads (#890, #1062):
PUT /sandboxes/{id}/files/{name}streams a raw body into the lease'sOUTPUT_DIR(local and node mode); the job reads{OUTPUT_DIR}/{name}andGET .../files/{name}returns the same bytes.GATEWAY_GPU_MAX_UPLOAD_BYTEScaps it (default 512 MiB, over-cap 413); the reply is{name,size,sha256}; a paused or frozen lease needs an explicit resume. Cancelling an upload cleans up its temporary file and descriptors. - GPU node-fleet consolidation (#1110): a node-mode front places a create on the fullest node that still fits (best-fit), so empty nodes stay empty and can be released.
POST /internal/gpu/drain {"drain": true|false}(orGATEWAY_GPU_DRAIN=1) stops a node admitting new leases while it keeps serving its own (503gpu_node_draining,Retry-After: 30); the capacity probe reportsdrainingandidle_since. - Verified-tier GPU (#1045, #1047): the
verifiedtier gets 2 GPU-hours per UTC month (TIER_VERIFIED_GPU_HOURS_MONTHLY) and 1 GPU lease (TIER_VERIFIED_GPU_LEASES), on a local plane and on a node-mode front; use above the allowance draws operator-granted GPU credit./v1/tierslistsgpu_hours_monthlyandgpu_leases,/v0/usagethegpu_monthly_allowance_*fields. Thefreetier still gets 403. - GPU image templates:
browser(full Chromium with WebGPU on NVIDIA, with NumPy; #889, #928),robotsim(MuJoCo, MuJoCo-Warp, Genesis, Newton, PyBullet and RoboSimStudio preinstalled; #906) and a Blender template with a product-shot demo and a GPU oracle (#970). - GPU pricing on the site (#1042, #1049, #1051): $0.10 per GiB-hour of busy time ($0.0000278 per GiB-second), billed per second of real use. Idle time and paused leases on pause-capable templates bill nothing; templates that cannot release the GPU bill while they hold it. The pricing page gains a GPU section and a calculator.
- CPU
blendertemplate (#881, #953): Blender 5.2.2 with the official Blender Lab MCP and a community MCP, software-rendered Cycles, EEVEE and Workbench without a GPU, one-command product-shot and turntable demos (blender-demo), a live Blender UI in the browser (blender-view) and a results gallery (blender-gallery). - CPU node registry (#915):
GATEWAY_CPU_NODESandGATEWAY_CPU_NODE_TOKENput several CPU nodes behind one front. A create lands on the node with the most free running slots, then disk headroom; each sandbox keeps its owner node, and control, exec, files, snapshot and fork calls route there.GATEWAY_CPU_NODE_STATESmarks a nodeactive,drainingordown. With no extra nodes the single-upstream path is unchanged. - WebSocket on data-plane port URLs (#958): an
Upgrade: websocketrequest tohttps://<port>-<sandboxID>.<domain>is proxied behind the same gates as HTTP (tokens, pause, credit, admission), so noVNC, Jupyter kernels and dev-server HMR work through the public URL. Adds thewebsocketsdependency. xlargeresource class, 16 vCPU / 64 GiB (#869, #972): opt-in; the enabled default stayssmall,standard.UPSTREAM_PROFILES(#877) declares which conformance profiles the upstream implements (core,snapshot,fork). A route of an undeclared profile answers 501upstream_profile_unsupportedbefore any upstream call or store write; the defaultcore,snapshot,forkkeeps today's behavior.- Template build
stepsbehindTEMPLATE_STEPS_ENABLED(#974): when set, a build start accepts the SDK'sRUN/ENV/WORKDIR/USERsteps. Default off: the 501 is unchanged. - Per-sandbox egress policy read-back behind
EGRESS_POLICY_ENABLED(#991; default off, off changes nothing): the canonicalallowOut/denyOutlists sent at create and onPUT /sandboxes/{id}/networkare stored and served back byGET /sandboxes/{id}/network. The flag also enables thenetworkOffshorthand on create, fork and PUT bodies, fork inheritance of the source's stored lists, a 400 for an internet allow combined with deny-all, andGATEWAY_DEFAULT_EGRESS=deny_all, which default-denies new sandboxes that carry nonetworkblock, except a create that explicitly setsallowInternetAccess: true(orallow_internet_access: true). - Tier-aware paused-sandbox retention (#907):
PAUSED_RETENTION_S, withTIER_FREE_PAUSED_RETENTION_SandTIER_VERIFIED_PAUSED_RETENTION_S, sets how long a sandbox may stay paused (default 7 days;0keeps forever). Expiry closes paused-storage metering at the deadline and deletes through the shared path; the lifecycle webhook keepskill_reason=retention. A node with no retention setting runs dry-run; an explicit legacyGATEWAY_PAUSED_RETENTION_DAYSkeeps live deletion. - Sandbox metadata (#935):
GET /sandboxes?metadata=k%3Dvlists only the sandboxes whose create metadata matches, andPUT /sandboxes/{id}/metadatamerges lifecycle tags into it (merge only, 16 KiB cap). - Operator tools: an AgentENV snapshot-store GC that is dry-run by default and never deletes a referenced snapshot (#886), and a maintenance-only
--applymode with stale runtime-cache retirement for the layer GC, which stays read-only by default (#930, #981). /demos/gallery on the site: reproducible cookbook demos with measured numbers, video and a one-command run (#878, #954, #960, #971).
Fixed
- GPU
accel.classpicks the card in node mode (#1109, #1087, #1092). The front echoed the class it was asked for and placed the lease on the least-seated node whatever that node served. It now places only on nodes whose capacity probe lists the class. A class no answering node lists is refused before anything is placed: 400gpu_class_unavailable, naming the classes served now, or the retryable 503gpu_node_unreachablewhile a node did not answer. An omitted class takes the class of the node the lease lands on, and create,GET /sandboxes/{id}and the list all report it. In retryable refusal bodiesclassisnullwhen the create named none. - GPU
cmdexec replies (#1107, #1113):result.stdoutandresult.stderrcarry the command's output (last 64 KiB of each), as for acodeexec; it used to sit only inresult.report, which keeps it too. libvgpu's[HAMI-core Msg(and[HAMI-core Warn(lines are removed fromcmdstderr (ERRORlines and stdout are untouched); new hard-cap leases start withLIBCUDA_LOG_LEVEL=1andCUDA_DEVICE_MEMORY_SHARED_CACHEset; and the worker interpreter's directory leadsPATHforcmdand forcodesubprocesses, so a barepythonorpipis found instead of exit 127. - GPU billing (#1055, #1056, #1066, #1071): an idle interval the activity sampler cannot classify is billed only for a
suspend_mode: "none"lease, which holds the card exclusively, and 0 for every other lease. Anonelease bills its whole residency exactly once in the durable ledger, and suspend spill, the pre-spill VRAM probe and teardown time are no longer billed as idle tail.metering.gpu_billing_mixreports the billed/analytics split. - GPU exec and boot (#1066, #1076, #1077): an exec that fails inside the gateway frees its job slot (every later exec used to answer "job in flight"). A worker boot that dies on the CUDA busy race is tried up to 3 times, then answers the typed 503
gpu_worker_boot_failedwithRetry-After; a node-mode front retries it on the next node or passes it through. 409s for a job pasttimeout_sand for a dead lease carryerror_code(gpu_job_timeout,gpu_lease_dead). - GPU exec input and verdicts (#890, #1086, #1089): each job starts with its own empty
report, so an earlier attempt's data no longer mixes into a new stdout (other variables persist across execs; a non-zeroreport.exit_codedoes not changeok). A missing, blank or whitespace-onlycode/cmdis a 400 on the public route, the node route and the front proxy, instead of an empty billed job. - GPU create defaults and validation (#901, #1063): a create without
accel.reservedefaults toexec; it defaulted tohold, so any child-process CUDA allocation hit OOM (holdstays an explicit opt-in). The 400 for an out-of-rangeaccel.idle_timeout_sechoes the value sent instead of'<invalid>'. - GPU capacity (#1078, #956):
seats_freecounts the 0.5 GiB leases admitted now (VRAM after footprints and margin, capped by the remaining seat limit) andmax_admit_gibis the largest single slice admitted now; unreachable-node and lost-reply refusals carrygpu_node_unreachableorgpu_lease_deadandRetry-After. A stalled GPU node no longer holds every tenant'sGET /sandboxesbehind the 300-second create timeout: list and locate reads query nodes concurrently underGATEWAY_GPU_NODE_LIST_TIMEOUT_S(default 5 s), and a timed-out node adds no rows and logs one warning. - GPU worker containers (#1071): a container caught between create and start is now removed, and containers of this gateway that no live lease owns are swept every 30 s, not only at boot.
GET /readyzon the internal listener fails 503 with a named reason while an admission gate refuses every start:disk_admission(withdisk_free_pct) orcapacity. The refused create carrieserror_codedisk_pressureornode_at_capacity(#913, #952).- A connect on a running sandbox only extends its timeout (#902): a connect whose deadline ends before the current one keeps the stored timeout, so an SDK-default connect no longer shortens a longer lease and gets the sandbox reaped. Resuming a paused sandbox and
POST /timeout(which may shorten) are unchanged. - The data plane's credit-exhausted 402 is a typed Connect error (#853): HTTP 402 with
{"code": "failed_precondition", "error_code": "credit_exhausted"}, so the stock e2b SDKs raiseCode.FAILED_PRECONDITIONinstead ofCode.UNKNOWN, which example agents retried as transient. Status and message are unchanged. - Retention and CPU-relay follow-ups (#952): the never-emitted webhook kill reason is gone, dry-run candidate counts are logged only when they change,
/internal/metricscaps retention candidates at 100 with an exactcandidate_count, CPU relay prefixes match exactly or at a slash boundary, and an owner node going unavailable no longer replaces the 499 reply of an abandoned create or resume. - MCP app: a completed command reporting
duration_ms: 12000shows 12 s, not 200 min (#824). - NVX upstream adapter (#876, #922): control calls pass
NVX_TIMEOUT, an uncertain daemon result never re-runs a command, and sandbox lifecycle records are persisted atomically, so ownership survives a restart, a failed start or a cancellation and is reconciled on recovery. - The MCP host serves
/llms.txt, which used to answer 404 (#861). - The deploy installs the off-host backup scripts and fails when
BACKUP_UPLOAD_CMDnames a script that is missing or not executable (#868).
Changed
- GPU admission defaults (#1075, #1081): the per-lease footprint drops its extra-context allowance (
GATEWAY_GPU_HARD_CAP_CONTEXTSfrom 1 to 0) andGATEWAY_GPU_K_RESIDENT_MAXdefaults toauto, derived per node as the lower of a VRAM bound and a host bound (free RAM at boot, CPUs); an integer still overrides it.seats_totalin the capacity view reports the derived value. - The
@trustednetwork preset includes the gateway's own API and MCP hosts, so an agent inside a trusted sandbox can reach the sandbox API (#898, #910). - Agent-readable MCP errors (#884): every JSON-RPC error carries a
hintnaming the next step; a create that dies in transport says it may or may not have landed (callsandboxes_listbefore retrying), and an exec that cannot reach the sandbox says a retry may run the command twice. - Site and docs: the site is redesigned (#900, #912); the GPU guide gains a hello-world quickstart and a lease lifecycle contract, with
accelin the served OpenAPI and llms files, and no longer says a GPU lease cannot pause (#1016); it also tabulates measured usable memory per slice (#1068, #1095), names the reserved exec variablex(#1074) and points at the current API host (#862); the Claude Code connector page lists common errors (#860).
0.0.13 (2026-10-14)
Fixed
- GPU hard cap (#838): the libvgpu preload surface (
/etc/ld.so.preload,/usr/local/lib/libvgpu.so,/usr/local/vgpu/libvgpu.so) is bind-mounted read-only per lease, so container root can no longer blank the preload and run uncapped child CUDA processes. Verified on real A10 hardware. - Dead-marking and upstream orphans (#593, PR #833): a sandbox is never marked dead unless the upstream delete is confirmed; orphaned upstream VMs are reconciled, so gateway/upstream drift no longer leaks VMs.
- Public ports can no longer wake a tenant-paused sandbox (#303, PR #834); envd-authenticated data-plane traffic still auto-resumes under the key cap.
- Encrypted off-host DB backup + restore drill (#252, PR #835): opt-in (
BACKUP_UPLOAD_CMD), encrypt-then-verify-then-upload, tamper-evident restore drill with plaintext sha256 inside the ciphertext. - Fresh-Lambda GPU node bring-up (PR #842): reuses a preinstalled nvidia-container-toolkit, installs uv python outside
/root(ProtectHome), provisions the host conda bind (GATEWAY_GPU_CONDA), and installs the health endpoint as a reboot-surviving systemd unit.
Added
- Per-key sandbox TTL cap (
keys.max_timeout_s, #656, PR #832).
Changed
- Gateway hostname moved to gateway.agent-next.com (PR #836).
0.0.12 (2026-10-13)
Added
- MCP
sandbox_workspace: one card listing the caller's machines, live apps, files and jobs (#760). - Ops: AgentENV snapshot-store managed-layer GC tool, dry-run by default (#695, #617).
Fixed
- Billing: a key whose CPU credit is exhausted can no longer start new process work (exec) during the grace window; it gets the credit error instead (#817).
- GPU artifacts: public file reads of a GPU lease are proxied to the owning GPU node with owner/path/size checks, so ordinary
OUTPUT_DIRfiles no longer 404 (#821). - math-verify demo:
--local --llmscratch remap, no hostrm -rf(#819).
Tests
- MCP timeout output is checked across both polling replies (#826).
0.0.11 (2026-10-13)
Changed
- MCP exec/file protocol semantics:
sandbox_execresults carryduration_ms— a float in milliseconds, measured around the command inside the job — for every exited/timeout command, andnullwhile the command is still running (GPU exec results andsandbox_exec_outputpolls carry noduration_ms). The job's exit file grew a third field (<code> <flag> <duration_ms>), and the gateway now reads it for every finished exec (before, only exit 124/137 were probed), which also changesoom_killed: it istruewhenever the sandbox's kerneloom_killcounter moved while the command ran, whatever the command's own exit code (the OOM killer may take a neighbour, not the command; flags can combine astimeout,oom), and no longer requires the top-level exit code 137.sandbox_files_readof a directory answers a normalisErrortool result witherror_code: "IS_DIRECTORY"andretryable: falseinstead of a-32000JSON-RPC error an agent would read as a transient sandbox problem. Invalid-params (-32602) type errors now name the field and the accepted types (command: expected a shell string or argv array).
Fixed
- MCP
sandbox_execon a GPU sandbox: the worker's own python (host conda or a template venv, the one with torch) is now first on the command'sPATH.python3 -c 'import torch'in thegputemplate failed withpython3: command not found. - MCP
sandbox_exec: a dropped upstream stream (peer closed connection without sending complete message body, about 1 in 450 execs on prod) no longer surfaces as an internal error. The finish check of a capped exec is retried; a dropped main stream hands the detached job off asrunningwith itsjob_id(#767). - MCP
sandbox_exec: the finish check of a capped exec is retried (up to 3 tries) on a transient 502/503/504 instead of handing a finished command back asrunningwith only its head (a 502 on about 1 in 200 calls on prod, #767). - MCP
sandbox_exec: when a capped exec cannot be finish-checked and is handed off asrunning, the gateway logs a warning with the reason (#767). - MCP
sandbox_exec: a command that ends within a moment of tripping the output cap now returnsexitedwith its tail. The call waits up to 1.5 s for the job's exit record before handing a capped exec off asrunning(was about 1 in 60 on prod, #767). - Signup without GitHub or JavaScript: the beta page's waitlist form is a plain HTML form post to
POST /v1/waitlist, which now also accepts form-encoded bodies (andPOST /v1/claimtoo), answering a browser with an HTML page that carries the claim token and thecurlclaim command; JSON clients keep the JSON contract. The console's "Sign in with GitHub" button links to the real sign-in route instead of looping back to/beta/, and the beta page documents the curl signup path. - Templates: PID 1 in a sandbox is
tini, not/bin/sh, so orphaned children are reaped instead of staying<defunct>(#693). AgentENV execs the image's/initas PID 1; every AgentENV template Dockerfile now installs a pinned, sha256-checked tini and writes that/init. Takes effect for sandboxes created from a rebuilt and re-registered template; running and paused sandboxes keep their PID 1. - MCP tool calls: arguments a tool does not declare, miss or give the wrong type are refused with an invalid-params error, NUL and control characters in commands, paths and templates are refused before they reach the sandbox, and error text is length-capped and shows control characters as escapes.
- envd port: an unauthenticated request now gets the same 401 whatever state the addressed sandbox is in; the stopped-sandbox 502 is answered only to a caller that presents the sandbox's token.
- Data plane: a paused sandbox created with
autoResumeoff (the default) still answers502(the status the SDK'sis_running()reads as stopped), but the body now sayserror_code: sandbox_pausedand "paused and autoResume is disabled" instead of "The sandbox was not found", which read as deleted. Killed and unknown sandboxes keep the E2B not-found body. Limits, home and personal-agents pages now state that the next request resumes a sandbox only when it was created withautoResume. - MCP
sandbox_exec: a command killed attimeout_sreturnsstatus: "timeout"(withexit_code124,timed_out: true, the output so far; the whole process group is killed), also fromsandbox_exec_output. When the sandbox cannot confirm how a killed command ended, the call staysrunningwith ajob_id(output capped like any running result) instead of guessing.
Added
- MCP owner round 3 (R13, R14, R15, R17):
sandbox_createsendsautoPause/autoResume, sotimeout_minexpiry pauses the sandbox and the next call resumes it instead of killing it; the owner's 4 MiBsandbox_files_readcase is pinned (the serialized-reply bound came with #715) and the read limit is documented; a runningsandbox_execreply is regression-tested at the documented 100,000 B per stream; an unknown sandbox, job or file is an error result witherror_code: NOT_FOUNDandretryable: false, not a retryable protocol error.
- MCP guest findings (owner claude.ai test, P2-c / R10):
sandbox_files_readandsandbox_files_writenow name the sandbox user (username=user, as the E2B SDKs do) so files are no longer created by root, with one retry without a username only for a template that has nouseraccount; the docker daemon being off untilstart-docker(thedockergroup is already set) is stated in the tool description and docs;ops/host_firewall.shrejects forwarded guest traffic to 169.254.169.254 (provider metadata) on NATed substrates, anddocs/guest-metadata-endpoint.mdrecords that the in-guest 169.254.169.254 on Firecracker is the VMM's own MMDS, not cloud metadata.
- MCP lifecycle and docs (owner claude.ai test, P2-a / P3-c / P3-d / R5-R9 / R11 / R12): a repeated or unknown-id
sandbox_killreturnsokwithalready_gone: true(E2B'sSandbox.kill()answersfalseon a 404) instead of -32002;timeout_minis documented as a hard lifetime from creation (not an idle window), its error speaks minutes (max 1440);sandbox_preview_urlreportspublicand every doc says previews are public unless restricted withnetwork.allowPublicTraffic=false/public:false;metersomit thegpu_*fields until the key has GPU usage; guest limits (no memory cgroup, IPv4 only, SMTP blocked, shared 169.254.0.21) and the concurrency limit are documented.
- MCP
sandbox_exec(owner claude.ai test, P2-b / P3-a / P3-b / R2): a command that outlivestimeout_sis killed with its process group and returns a normal result (timeout,exit_code124,timed_out: true, partial output) instead of a protocol error; output over 256 KiB per stream comes back as head (192 KiB) + marker with omitted/total byte counts + tail (64 KiB), withstdout_file/stderr_filenaming the full output; an out-of-memory kill isexit_code137 withoom_killed: true(the kernel'soom_killcounter moved); a backgrounded child holding the output pipes no longer keeps the jobrunningafter the main process exited.
- OAuth sign-in for new users: the authorize page is redesigned (one-line value, safety promise, a "Details for developers" panel with client id, scope, resource and issuer) and offers Continue with Google, Continue with GitHub and an emailed 6-digit code, each shown only when configured (
OAUTH_GOOGLE_CLIENT_ID/SECRET,OAUTH_GITHUB_CLIENT_ID/SECRET,OAUTH_EMAIL_RESEND_API_KEY+OAUTH_EMAIL_FROM). A first sign-in creates a free-tier account; API-key paste stays available. Spec:docs/specs/mcp-oauth.md.
POST /sandboxes/{id}/forkaccepts a paused source (B-09): the gateway checkpoints the paused source (resume, snapshot, pause again) and forks through that snapshot via the create-from-snapshot path, so the child always reflects the pause-time disk, never a stale older snapshot.countis honored; the source ends paused under its original pause label, but runs (and is metered) for the checkpoint and needs a free running slot like a resume. An abuse-paused source is refused with 403 like every other verb; when the checkpoint cannot be taken the fork is a 409.Idempotency-Keyheader onPOST /sandboxes,POST /v2/sandboxesandPOST /sandboxes/{id}/fork(CARD-03, boat's documented contract): the first request binds the key to the created sandbox and replays the exact original response; a retry while the first is in flight answers409 idempotency_in_progress; the same key with a different body answers409 idempotency_key_reused. Bindings are scoped per API key, hashed over the request body as it is sent upstream (the rewritten create body; the raw client body plus the source sandbox id on fork), kept 24 h (GATEWAY_IDEMPOTENCY_RETENTION_S, swept by the idle reaper), and released immediately when a create fails before the sandbox exists (advisory quota/ rate refusals included). A create lost in transport reconciles against the upstream by a gateway-stamped operation id in the create metadata — an unrelated sandbox is never adopted; when the outcome stays unknown the key parks recoverable and the next claim (also after a restart) reconciles it before creating. Omitting the header keeps the previous non-idempotent behavior byte-identical.
- Torch-free GPU worker reserve (opt-in):
GATEWAY_GPU_WORKER_RESERVE=cudaboots the lease worker's VRAM reserve via a ctypes/libcuda backend (reserve-cudaargv) instead of torch, so worker images no longer need the ~4 GB torch stack just to hold a slice. Defaulttorch— existing images and argv unchanged.cudarequiresGATEWAY_GPU_HARD_CAP=1(without torch's per-process fraction the slice cap would be advisory; enforcement rides libvgpu + the NVML watchdog). Reserve/release semantics, canary bytes and every protocol file are identical to the torch path. Spec:docs/specs/gpu-image-standard.md.
Fixed
- Forking a paused sandbox answered 409 on production (#644 shipped snapshot-the-paused-source, which the real upstream refuses with 400 "cannot be snapshotted from paused state"; the test fake allowed it). The gateway now resumes the source for the checkpoint and pauses it again, and validates the fork body before it resumes anything.
sandbox_execreaps the processes atimeout_skill leaves behind: the detached job bash is now a child subreaper (when the template has python3), so the killed command and its children no longer stay zombies under a sandbox PID 1 that never reaps. The command still starts with SIGPIPE and SIGXFSZ at their defaults (python3 ignores both and that survives exec).
sandbox_files_readreplies are cut to fit 6 MiB on the wire (truncatedtrue when cut): quotes, control characters and non-ASCII text grew up to 6x when JSON-escaped and MCP clients closed the connection above ~8 MiB; the text block no longer ASCII-escapes non-ASCII characters.
- The auth host's nginx allowlist now forwards the Google and email sign-in routes (
/oauth/google,/oauth/google/callback,/oauth/email,/oauth/email/verify); before, nginx answered them 404 on prod. - GPU exec results carrying NaN/Infinity/-Infinity (a tenant's negative test) no longer 500 the node's
POST /internal/gpu/leases/{id}/exec(ValueError: Out of range float values are not JSON compliant: nan), which the front turned into a 502 while the lease was healthy. Every surface that returns tenant-derived exec JSON — the node exec verb, the front router pass-through, the publicPOST /sandboxes/{id}/exec(both node-mode and local plane) and the MCPgpu_lease_exectool — replaces non-finite floats withnulland stamps a top-levelnon_finite_replaced: <count>when it replaced any; finite results keep the identical shape with no extra key.
- Site UX (#816): no page of sbx.agent-next.com overflows horizontally on a 390px phone any more (the console nav is reachable without a hamburger workaround), and the free tier is no longer overstated — the banner/hero read "Public beta — 10 sandbox-hours free" with the 100-hour grant correctly tied to the $0 card check; the quickstart stops contradicting itself on waitlist vs instant GitHub key, docs and llms files settle on one env-var name, and the jq dependency is stated.
- Gateway UI and docs (#815): the MCP Apps console card has a back affordance and labels the template next to the sandbox short id; exec results show their duration; llms-full/llms.txt/mcp-connect carry the
duration_ms(milliseconds) contract, a StrictMode double-create warning and the MCP endpoint pointer, and drop the stale "no OAuth server yet" line. - Examples (#813): a shared
examples/_lib/pick_model.pypreflight (model lane routability, API-host auth, template package versions) behind--preflight; example hosts default toapi.sbx.agent-next.cominstead of the deadsandbox.agent-next.com; the mjwarp download is ported off the removed REST files route to the SDK; the mujoco sweep adapts to the template's installed package version; a nightlyexamples-preflight.ymlworkflow guards the drift. - SLA prober (#792): incident diagnostics use current samples.
- Dev (#789): the GNU-coreutils job-shim exec tests skip on darwin (BSD
statbroke the shim's output polling, failing or deadlockingmake check); Linux/CI unchanged.
0.0.10 (2026-10-12)
PID 1 in a sandbox is tini, so orphaned children are reaped and no longer stay <defunct> (#693, #736, #748). AgentENV starts the image's /init as PID 1 and fell back to /bin/sh, which never wait()s for reparented children. Every AgentENV template image (agent-full, desktop, math-agent, wine, manim, photo, app-preview, code-interpreter, eda, world-builder, omarchy) now ships a pinned, sha256-checked tini /init. On prod the default, agent-full (all sizes), desktop, math-agent, wine and manim templates are rebuilt and live; running and paused sandboxes keep the PID 1 they booted with.
0.0.9 (2026-10-12)
Activity renews the upstream lease of autoPause sandboxes (#735). Exec, files and port traffic through the data plane, an open stream, and connect (v1 and v2) now extend the upstream keep-alive to the idle floor (or a longer client timeout) from now, capped by SANDBOX_MAX_TIMEOUT_S; before, an active sandbox created with a short timeout could be paused or expired upstream while in use. Renewal is throttled and best effort, never fails the request, and a client set-timeout, refresh or connect timeout is never shortened by it. Idle sandboxes still auto-pause after the idle time, and sandboxes created without autoPause are unchanged.
0.0.8 (2026-10-11)
MCP owner round 3 (#719). sandbox_create now sets autoPause and autoResume: a sandbox whose timeout_min runs out is paused (files kept) after the 5-minute idle floor and the next call resumes it, instead of being killed (R13). The owner's 4 MiB sandbox_files_read case is pinned and documented (the reply bound itself came with #715) (R14). A running sandbox_exec reply is regression-tested at the documented 100,000 B per stream (R15). An unknown sandbox, job or file returns an error result with error_code: NOT_FOUND, retryable: false (R17).
0.0.7 (2026-10-11)
MCP sandbox_exec now answers a timeout_s expiry with a normal tool result instead of a -32000 protocol error: status: "timeout", timed_out: true, exit_code 124 and the stdout/stderr captured so far, with the command's whole process group killed (a child that started its own session via setsid survives); sandbox_exec_output reports the same status for a job killed at its deadline. When the sandbox cannot confirm how a killed command ended, the call stays running with a job_id and capped output so a poll can classify it. Ships with the exec, lifecycle and guest changes of #696, #697, #698 and the follow-up #718; clients that switch on status must handle timeout.
0.0.6 (2026-10-11)
Deployed to prod (api.sbx / mcp.agent-next.com) 2026-10-11.
- OAuth 2.1 authorization server for the MCP endpoints (#670, #667): DCR + client ID metadata documents, PKCE S256, RFC 9207
iss, refresh rotation with replay revocation. Issuerhttps://auth.agent-next.com. ChatGPT and Claude can connect with OAuth;X-API-Key/Authorization: Bearer <api-key>keep working unchanged. - MCP
sandbox_exec: a clean exit now reportsstatus: exited, exit_code: 0on the first call (proto3 JSON omits a zero exit code) (#673). - Docs: claude.ai custom connector setup (#671).
The three releases above were tagged without a version bump, so /healthz and the MCP serverInfo kept reporting 0.0.5; the bump to 0.0.8 landed afterwards and gateway/tests/test_release_check.py now fails when pyproject.toml trails the latest tag. Their detailed entries are still listed under Unreleased.
0.0.5 (2026-10-09)
Added
- Per-sandbox GPU worker templates: operator allowlist
GATEWAY_GPU_TEMPLATES, selected withaccel.templateonPOST /sandboxes(unknown name -> 400), per-templatesuspend_mode(nonefor JAX-style workloads),templateIDreflecting the template, and listing inGET /templatesandGET /gpu/templates. - Paused-sandbox retention (owner decision 2026-10-09): the reaper deletes sandboxes paused continuously for
GATEWAY_PAUSED_RETENTION_DAYS(default 7;0= off), freeing the upstream disk/snapshot each pause holds — without it the artifacts accumulate until the node's disk fills (a production node, 2026-10-09: 132 GB of paused artifacts, sandbox create 500ed for 30 min). Running sandboxes are never candidates; each kill is metered once (gateway_paused_retention_kills_total,killevent with kill reasonretention); after deletionGET/connect/resumeanswer404not found.GATEWAY_PAUSED_RETENTION_DRY_RUN=true(first deploy) makes the sweep log-only. - GPU plane (container mode): a gateway restart or redeploy no longer kills live GPU leases. The gateway re-adopts its own worker containers and lease records at boot, detaches instead of killing at graceful shutdown, and reaps what it cannot adopt consistently with
dead_reason. The outage itself is never billed (shutdown checkpoint / 5 s alive stamp). python -m gateway.gpu_orphans: list/reap detached GPU worker containers of retired gateway ids (dry run by default).
Changed
- GPU billing semantics (owner decided 2026-10-08: bill actual GPU use):
billed_sand the GPU credit bucket charge exec intervals plus idle-resident intervals in which anActivitySamplersaw GPU activity from the lease, plus resume toggles. Truly idle and suspended time stay free. Without a sampler (or when it cannot tell) idle residency bills in full. Stray compute on an idle lease is flagged (stray_compute). Seedocs/SPEC-gpu-p0-launch.mdP0-2.
Fixed
- GPU exec results from the built-in workers carry an HMAC under a per-lease key that the worker unlinks at startup, so the tenant-readable per-job nonce no longer forges a result (C4-nonce-leak). Tenant code in the same process can still recover the key by introspection.
Pull requests in this release
- Sandbox API and E2B parity: #458 lifecycle parity, #465 signed URLs, #456 data-plane Content-Length, #486 AgentENV /proxy routing, #479 + #491 snapshot delete/async create, #392 default user + code-interpreter-v1, #426 thread-safe Store, #497 real-user red-team fixes (status link, quota body, not-found wording, bodiless connect, no upstream 5xx text to clients).
- Accounts and credit: #440 self-serve keys with free / card-verified tiers, #386 console API surface, #377 separate GPU credit bucket, #495 paused retention.
- GPU plane: #422 + #429 unforgeable exec results, #437 + #432 re-adoption and own-container sweep, #439 + #473 VRAM caps, #444 node mode, #428 templates, #445 + #462 catalog images, #452 #453 #455 #471 #484 fixes, #430 Lambda launcher.
- Interfaces: #414 sandbox-mcp (
/mcp/v1), #287 #288 #449 #461 sbx CLI, workflow and one-version skill. - Templates: #419 #421 agent-full (zstd, droid + devin CLIs), #466 math-agent.
- Ops and SLA: #446 + #492 SLA prober with GitHub-issue alerts, #488 front-to-DO migration.
- Tests, bench and docs: #447 #457 #472 #485 #490 #496 #476 #478 #483 #489 #416 #425 #493.
0.0.4 (2026-10-07)
Create, fork and egress bounds the red team found missing, plus network presets and named resource classes (#313).
Added
- One core, three interfaces: the
sbxCLI, the/mcp/v1remote MCP and the generated agent skill (skills/sbx-cloud-agent/SKILL.md) are checked against one operations manifest (tools/sbx/src/sbx/ops.py) by drift tests in both suites, and report one release version —sbx --version, the MCPinitializeserverInfo (was the hardcoded0.1), the SKILL.md front matter andGET /healthz. The skill is generated from the manifest bytools/sbx/gen_skill.py(spec:docs/specs/one-core-interfaces.md). - Network presets for
allowOut:@packages(package registries) and@trusted; expanded gateway-side before the upstream call. - Named resource classes (
small,standard,large) at template registration, with an allowlist viaRESOURCE_CLASSES.
Fixed
- Sandbox keep-alive is capped at
SANDBOX_MAX_TIMEOUT_S(default 24 h) on create, fork, set-timeout, resume, connect and refreshes (duration); an over-cap value is a 400 and never reaches the upstream. The injected default timeout on create, fork and resume is capped too (#286). - Create and fork sizing is enforced against
TEMPLATE_MAX_*,RESOURCE_CLASSESandTEMPLATE_MAX_DISK_MB; fork bodies are limited to a known, sized field set (#286). - Egress allow/deny entries are parsed and normalised; loopback, link-local, RFC 1918, CGNAT, multicast, reserved and wide (
*,0.0.0.0/0,::/0) targets are refused unlessALLOW_TENANT_WIDE_EGRESS=1(#301); 6to4 and Teredo prefixes and the RFC 6762 private zones (intranet,private,corp,home) are refused alongside the existing local zones. - Public export: the IP scan accepts addresses that cannot name a host (multicast, reserved, short-prefix CIDR bases) while still failing CGNAT hosts; the new gateway tests ship in the cut; private GPU probe and state-db references are rewritten.
Changed
- Requests that previously succeeded with an over-cap timeout, oversized resources or a private/wide egress target now get a 400.
0.0.3 (2026-10-07)
Fixed
- Idle auto-pause honors the client's timeout (#311). The idle window is the larger of 300 s and the timeout set at create,
set_timeout, refresh or resume, capped at 24 h, so long in-sandbox jobs are no longer paused mid-run. Resume accepts an empty body (E2B-compatible). Non-finite timeouts get a 400 before any upstream call, and huge integers are clamped. Upstreamset_timeoutcalls are serialized per sandbox under an overall 30 s deadline. - GPU lease results: the host re-reads a result file that is visible but not yet complete (for up to 2 s) instead of failing the exec. The worker fsyncs before its atomic rename (#330).
0.0.2 (2026-10-07)
Sandbox dogfood fixes (#261, landed on main via #315).
Fixed
accel.classis validated against the GPU classes the host actually has. An unknown class (e.g.nvidia.h100on a 5090-only host) is rejected. Class discovery fails closed: setGATEWAY_GPU_CLASSESwhen nvidia-smi is unavailable.GET /sandboxeslists GPU leases next to CPU sandboxes. Every row carries the fields the stock E2B SDK requires;Sandbox.listno longer raisesKeyError: 'clientID'. Malformed stored metadata no longer makes the list return 500.
0.0.1 (2026-10-06)
First tagged release: the multi-tenant E2B-compatible sandbox gateway plus the GPU sandbox launch set, deployed as the sbx-local unified CPU+GPU sandbox on the RTX 5090 host with a public endpoint.
Added
- Multi-tenant gateway control plane: tenant API keys, per-key quotas, sandbox lifecycle metering, idle auto-pause (#3); abuse protection (#6); metrics, health checks and alert rules (#7); waitlist signup, invite approval and usage balance (#8); production deploy tooling (#16).
- E2B wire compatibility for the stock e2b SDKs — wire envelope, retriable 429s,
envdVersioncontract, list pagination, template and lifecycle gaps (#127, #139, #141, #143, #156); E2B webhooks + usage self-budget (#185). - Sandbox snapshot + fork APIs (#117, #119); tenant-scoped template registry with OCI
from_imagebuilds (#143). - Hand-authored public OpenAPI 3.1 spec at
/openapi.jsonplus/llms.txtand/llms-full.txt, parity-tested against the route table (#148);/healthzreports the release version. - GPU sandbox plane: P0 launch gates (container isolation, durable billing, public execution), tenancy + node-token authorization on internal GPU routes, authenticated MCP stdio bridge, fail-closed public host-backed execution (#176, #187, #199, #203, #204).
- Concurrent GPU lease plane with honest billing and a job-write boundary; GPU usage drawn against credit; public tenant policy enforced at execution admission; per-process VRAM cap with process-group kill on destroy (#224, containing #202, #215, #221, #222).
- Demo surface: read-only
/modelsmount viaGATEWAY_GPU_MODELS_DIRand owner artifact downloadGET /sandboxes/{id}/files[/{name}](#224). - CI: pytest-xdist gateway suite (~2.5 min on the self-hosted pool, was >45), docs-only PR gating that classifies both sides of renames, main runs never cancelled (#238, via #224).
Fixed
- GPU billing reads the exec start clock before publishing the job, so a worker finishing first cannot shrink the bill; Unicode artifact names and minimum-slice (0.5 GiB) worker boot (#224).
- Container host RAM capped at the admitted spill size + 2 GiB base; billing uses gateway-observed exec time; worker file reads refuse symlinked or non-regular entries; worker state tensor is a canary so the capped VRAM slice stays usable (#224; red-team items M1, C4, RT8).