Create a Space

Create a Space on a computer you own from the app, the CLI or code: any image, progress, cancel, GPUs, adding an existing computer by address, then list, delete or remove it.

A Space is one desktop on one of your computers. Ids are the sandbox refs cua sb ls prints (local:<name>, aws:<name>, direct:<host:port>, relay:<machine-id>); a bare name works when it matches one Space. To connect a whole computer first, see Add a computer.

Create a Space in the app#

  1. Press New Space in Cua Spaces.
  2. Walk through Choose a system, Resources and Options. Summary shows what you picked.
  3. Press Create Space. The Space appears in your list with a progress bar and is ready when the bar finishes.

Advanced in Choose a system picks the kind (container, virtual machine or microVM) and the runtime. See Space kinds.

If the create fails, see Troubleshoot.

Add an existing computer by address#

Choose Connect by address in New Space, or register a computer from code. This example starts the canonical Linux image with a token:

TOKEN=$(openssl rand -hex 24)
docker run -d --name my-space -e CUA_ENV_TOKEN=$TOKEN -p 127.0.0.1:3211:3211 \
  ghcr.io/trycua/linux:24.04

Register it and run a command in it (use http://127.0.0.1:3211 and your $TOKEN):

import asyncio
import cua
 
async def main():
    spaces = cua.embedded().spaces()      # or cua.connect().spaces()
    info = await spaces.add("http://10.0.0.5:3211", "TOKEN", "lab")
    space = await spaces.space(info.id)
    out = await space.bash("uname -a", None)
    print(info.id, out.stdout)
 
asyncio.run(main())

The program prints the Space id (direct:127.0.0.1:3211) and the guest's uname -a. The Space is now in the shared registry, so the app and the CLI see it too:

cua --version
cua auth status
cua spaces ls

Clean up the container with docker rm -f my-space.

Create a Space from code#

created = await spaces.create(cua.SpaceCreateOptions(cpus=2, memory_mb=4096))
space = await spaces.space(created.space.id)

Runs ghcr.io/trycua/linux:24.04 (or image) with a fresh env token. on, kind and runtime work as for sandboxes (see Space kinds); without on, the Space runs where cua config get default.on says (local unless you changed it; the Spaces app sets it in Settings, Default location). cua runtime doctor shows what this host has.

reuse=True returns a registered, reachable Space in the same location first (created.reused says so). With wait=False, created.pending_id names a Space that is not ready yet.

A Space from any image#

create takes image, command, env and services. An image that serves MCP becomes a Space of that service, with no cua-spacesd:

created = await spaces.create(cua.SpaceCreateOptions(
    image="<your-mcp-server-image>",
    command=["python", "-m", "acme_mcp", "--port", "8765"],
    services={"mcp": 8765},
))
space = await spaces.space(created.space.id)
tools = await space.list_tools("mcp")
result = await space.call_tool("add", '{"a": 2, "b": 3}', "mcp", None)
Works on every SpaceNeeds cua-spacesd in the image
Lifecycle, declared services, tunnels, public URLs, list_tools / call_tool with servicebash, files, streaming, presence, teleport, agent runs, the driver tool registry

Spacesd features on an image without it fail with CapabilityMissing. The canonical images and computers set up with cua host setup run cua-spacesd, and so does any OCI image you create on this computer as a container or microVM (alpine:3.20, node:20): a static cua-spacesd is put into it and runs beside the image's own command.

In the cloud, command and env work on container and VM images alike. Keep secrets out of env.

Progress#

A macOS Space downloading its image: bytes, rate and time left

The app shows a progress bar, and under it while the image downloads 4.2 of 22.1 GB · 85 MB/s · about 4 min. cua spaces create prints the same on one updating line. In code, pass a listener:

class Show(cua.SpaceCreateListener):
    def on_progress(self, p):
        print(p.space, p.phase, p.bytes_done, p.bytes_total, p.bytes_per_second)
 
created = await spaces.create_with_progress(cua.SpaceCreateOptions(image="macos"), Show())
FieldValue
phasepreparing, pulling, creating, booting, waiting_for_services, connecting
fraction0 to 1, when the step has one
bytes_done, bytes_total, bytes_per_secondwhile an image downloads
spacethe id the Space will have (local:<name>)

macOS images report bytes with a Lume whose pull API reports them (downloadedBytes); an older Lume reports only a percent.

Cancel a create#

Cancel pressed mid-download: the Space is being cleaned up

  • App: Cancel in the toolbar of a Space that is being created.
  • CLI: Ctrl-C during cua spaces create or cua sb create (exit code 130), or from any terminal:
cua spaces cancel local:space-1a2b3c
  • SDK: cancel_create takes the create's create_id, the id the Space will have (space in every progress report) or its name, and returns once the cleanup is done. The create fails with CuaError.Cancelled.
outcome = await spaces.cancel_create("local:space-1a2b3c")
print(outcome.state, outcome.message)  # cancelled, not_creating or already_created

Sandboxes: await sandboxes.cancel_create(name) returns what was removed, or None. A cancel works across processes and after a daemon restart.

RemovedKept
The VM or container and its disks, the cloud claim, the relay computer, the registry entry, the create's journalAnything that existed before the create
Partial downloadsFinished image layers in the engine's cache (Docker's; Lume's when its cache is on), so the next create resumes

A Lume without POST /lume/pull/cancel cannot stop a pull: the create still cancels, and the image finishes downloading in the background as a reusable base.

GPU acceleration#

cua spaces gpus
cua spaces create macos --gpu paravirtual

--gpu alone (or gpu="auto") picks the runtime's own option; the SDK takes SpaceCreateOptions(gpu=...) and SandboxCreateOptions(gpu=...), and spaces.gpu_support(on) lists what exists here.

RuntimeOptionWhere
Lume (macOS VMs)paravirtual, GPU acceleration (experimental)Apple silicon
QEMUvirglLinux hosts whose QEMU has virglrenderer, egl-headless and a render node; none on macOS
ContainersnvidiaLinux with the nvidia runtime, runc (not gVisor); none on macOS (Docker runs Linux in a VM without GPU access)
ModalT4, L4, A10G, A100, H100
E2B, Daytonanone

New Space, Resources: the GPU acceleration (Experimental) checkbox for a macOS Space

In the app it is the GPU checkbox on the Resources step of New Space. For paravirtual, cua applies the unrestricted paravirtualized GPU feature level before each start of the VM and removes it when the last GPU VM is deleted, if cua set it. Metal apps in the guest may still need the per-workload shim: Unlock Metal for a workload.

List, delete, remove#

cua spaces ls

Lists registered Spaces and, when you are signed in, your computers on the relay (relay:<machine-id>, no add needed). Register or forget one from a shell:

cua spaces add 10.0.0.5:3211 --token "$CUA_ENV_TOKEN" --name studio
cua spaces rm studio

A Tailscale address works here too (its IP or MagicDNS name as the host:port): cua spaces add reaches it like any other address and the Space registers as direct:<addr>. That only attaches one already-running Space. To host Spaces for on="host:<name>", set the computer up with cua host setup.

spaces.delete(id) deletes a Space's sandbox and forgets it (a Space added by address is only forgotten). spaces.remove(id) forgets a Space without touching it.

In TypeScript the methods are create, delete_, add and remove, with options from @trycua/cua/spaces. Agents use the MCP tools create_space, delete_space, add_space and remove_space (What your agent can do).