Create capacity and claim from it
Apply a named pool, claim sandboxes from it, keep a sandbox across processes, and bound lifetimes with TTLs.
Apply a named pool, claim sandboxes from it, keep a sandbox across processes, and bound lifetimes with TTLs.
Sandbox.create(image, local=False) needs no pool. Create one when you want a
fixed name that other processes or teams claim from, a static warm size, a
creation-age TTL, or Terraform (Terraform). Pool
names are lowercase DNS labels, unique across all accounts.
import os
from cua_sandbox import Image, Pool, PoolOptions, SandboxSpec
pool = await Pool.apply(
os.environ["CUA_POOL_NAME"],
SandboxSpec(image=Image.linux(), cpu=4, memory_mb=4096, services={"env": 3211}),
PoolOptions(replicas=1),
)
print(f"Pool ready to claim from: {pool.name}")Pool.apply creates or updates the pool and its template. SandboxSpec is
what the sandboxes run; PoolOptions is how capacity is kept
(Pool reference). PoolAccessDeniedError
(403) usually means another account owns the name.
A pool fixes the image, command, services and size, so pass only the pool:
cua sb create --on cloud --pool my-team-desktop --name my-task
cua sb exec my-task uname -a
cua sb rm -f my-taskFields you pass with a pool must match its template, else PoolSpecMismatch
shows the diff; CloudOptions(pool=..., apply=True) (CLI --apply) writes
them to the template instead. A released sandbox may be recycled, so copy
results out first.
| Call | Behavior |
|---|---|
async with pool.claim(name=None, service="env") | Reattaches to claim name if it exists, else creates one; waits for service; releases on exit |
await pool.claim() | A connected Sandbox: close() releases it, disconnect() keeps the claim |
await pool.create_claim(name=None) | Creates without waiting. The handle has wait(), renew(shutdown_time), release() and to_dict() (for Sandbox.from_dict); you own cleanup |
await Pool.check(name, spec) | Raises PoolSpecMismatch when the set fields of spec differ from the template |
await Pool.apply_template(name, spec) | Writes those fields to the template; the pool keeps its capacity |
A failed claim releases a claim it created, never a preexisting one. Images
without cua-spacesd pass their own service=.
Name the sandbox, do not release it, and extend its lease for idle periods
(TypeScript: sb.keepAlive(...), sb.detach(), sandboxes().connect(name)):
sb = await Sandbox.create(image, local=False, name="my-task", keep_alive_minutes=120)
reference = sb.to_dict() # store it (JSON-serializable)
await sb.disconnect() # the sandbox stays held
sb = await Sandbox.from_dict(reference) # later, in another process
await sb.keep_alive(minutes=120)
await sb.close() # releases itcua sb keep-alive my-task --for 2h
cua sb info my-task| Deadline | Applies to | Set with | Renewed? |
|---|---|---|---|
| Sandbox TTL | Every cloud sandbox | CloudOptions(claim_ttl=...), --claim-ttl; keep_alive | Yes, while a process holds it |
| Creation-age TTL | Your pools and their claims | ttl_seconds_after_created= | No, counts from creation |
| Idle TTL | Your pools | PoolOptions(idle_ttl=...) | Deletes the pool after this long without claims |
from cua_sandbox import CloudOptions, Image, Sandbox
sb = await Sandbox.create(Image.linux(), cloud=CloudOptions(claim_ttl=30 * 60))
await sb.keep_alive(minutes=120) # an absolute shutdown time two hours from nowpool = await Pool.apply(
Image.from_registry(IMAGE),
name=POOL_NAME,
replicas=1,
ttl_seconds_after_created=86400, # the pool deletes itself after 24 hours
)
async with pool.claim(name=CLAIM_NAME, ttl_seconds_after_created=3600) as sb:
...poolTtlSeconds in poolOptions({...}); in Terraform,
ttl_seconds_after_created on fleets_pool.Delete it after its users have released their sandboxes. Deleting removes its template and remaining sandboxes; deletion is asynchronous.
await pool.delete() # the Pool from Pool.apply() also deletes its template