Skip to content
DocsAll docs
Console

Workspaces

Create, start, stop, and delete workspaces, the development machines that you work in.

Last updated

On this page

Overview

A workspace is your sandbox: an isolated machine that is kept while it is stopped. You start it, stop it, and connect to it, with your packages and SSH access. Its home is kept until you delete the workspace. It has the parts of an IDE without the window: a file system, a toolchain, a terminal, processes, and git. You open the same workspace from your terminal or editor over SSH.

Each workspace has its own kernel, file system, and network stack, with allocated vCPU, memory, and disk. A workspace belongs to you or to a team. Its name is unique for that owner, and the platform also gives it an id that starts with ws-.

PartDescription
Home/home/kl. The disk of the workspace. It holds all files, caches, and settings, and it is kept while the workspace is stopped.
Source directory~/workspace. Editors open this directory.
PackagesThe toolchain, from nixpkgs. See Packages.
Tool serverThe interface for programs: files, commands, and processes on port 7788.
SSHThe interface for terminals and editors. See Connection.
ResourcesUp to 4 vCPU and 8 GB of memory.

Workspace and capsule

Other platforms use the word sandbox for an isolated machine that keeps its files while work continues. In Kode, that machine is a workspace. A capsule is a different object: a short-lived container for one command.

AspectWorkspaceCapsule
UseLong work: you edit, run, and test code.One command or one shell.
LifetimeUntil you delete it. It stops and starts again.Until its main process exits.
FilesKept while it is stopped.Gone at the end.
AccessTool server and SSH.The output of its command, or one attached shell.

To do these tasks with an AI agent, see Agentic workflows.

Workspace types

A workspace is standalone or managed. The type is set at creation and does not change.

AspectStandaloneManaged
Created withPOST /v1/workspacesPOST /v1/desks/{id}/workspaces. See Desks.
Started, stopped, and deleted byYou, directlyIts desk
Reaches an environmentNoThe desk's environment
DeletedWhen you delete itWhen you delete it in the desk, or when the desk is deleted
desk fieldEmptyThe desk's name

A standalone workspace cannot join a desk later. To work with an environment, create a managed workspace in a desk.

Creation parameters

ParameterRequiredDescription
workspaceYesThe name. Use team/name for a team workspace.
repo and branchNoA repository (owner/name) and a branch to clone into the workspace. Set both.
packagesNoThe packages to install. See Packages.
envNoVariables. A value of ${{ secrets.NAME }} refers to a secret. In a team workspace, your personal secret in the team comes first, then the secret of the team. See Variables and secrets.
ttl_secsNoThe time to live in seconds. See Lifecycle.

States

stateMeaning
creatingNot running yet: waiting for capacity, installing packages, or restarting on another node after its node was lost (placement is null).
readyRunning and reachable.
stoppedNot running. The home is kept.
errorThe definition is not valid.
deletedBeing deleted.

The state also has packages_status (installation progress), placed (NoCapacity while the platform has no room), degraded (StorageUnavailable while its storage moves), idle_since, and expires_at.

Lifecycle

  • In a desk. A workspace in a desk is controlled through its desk. Starting or stopping the desk starts or stops it, and deleting the desk deletes it. Its desk field names the desk.
  • Stop. All processes end and the home is kept. Detached processes started through the tool server start again with the workspace.
  • Idle stop. A workspace in a desk stops when the desk is idle. Any other workspace stops after 30 minutes without use. An SSH connection or a tool server request counts as use. Using an idle-stopped workspace starts it again; a workspace that you stopped stays stopped.
  • Machine loss. If the machine that runs a workspace fails or is reclaimed, the platform starts the workspace on another machine. Processes and memory are lost. Data that the workspace synced to disk is kept. Detached processes start again.
  • Storage failure. If a storage machine fails, the storage serves the home from its second copy and the workspace keeps running.
  • Time to live. The platform deletes a workspace that is unused for ttl_secs (600 to 2592000). A running workspace, or one whose desk is awake, is never deleted.
  • Delete. The workspace and its home are removed. Snapshots stay.

Clone

A clone is a new workspace that starts with a copy of the live state of a workspace. The platform makes the copy at the time of the request, also while the source runs. A clone is not a snapshot, and it does not change the snapshot lineage.

The copy is independent: changes in the clone do not change the source, and changes in the source do not change the clone. The clone call answers 202 and starts the new workspace at once. The new workspace has the team and the region of its source, and it uses the same volume.

A clone takesA clone does not take
The files of the homeThe variables
image, packages, and locksThe desk, unless you clone with kode desks clone-workspace, which puts the copy in a desk. The new workspace has no time to live.

The answer has the field based_on. It names the copy that the clone started from.

FieldDescription
snapshotThe copy that the clone started from.
atThe time of that copy. Absent for a copy that this request made.
age_secondsThe age of the copy in seconds. Always 0, because the platform makes a new copy for each clone.
interruptedAlways false. The field stays so that the answer keeps its shape.

The copy shares its data with the source until either changes, so a clone of a large home is quick.

ParameterRequiredDescription
toYesThe name of the new workspace. It follows the rules of a workspace name, and is unique in the team of the source.
taskNoA note on what the copy is for. Up to 200 characters.

A clone counts as a new workspace against the quota of the owner.

To do these tasks with an AI agent, see Agentic workflows.

Restore

A restore makes a new workspace from a snapshot of a workspace. You name the snapshot by a reference: a volume name, name@N, or the id of a snapshot. The snapshot must be ready. To restore a volume of a team, give the team. A volume of another owner is never restored: the call answers 404. A restore moves the expiry of the snapshot to a full default lifetime from now.

The new workspace is on the volume of the snapshot. The source workspace can be deleted. A restore then attaches the detached volume again.

The new workspace takes the definition that the snapshot froze: image, packages, locks, and resources. A value in the request replaces the frozen value. An unchanged package keeps its lock, and a new pinned package is resolved.

The new workspace belongs to you. It is in the team of the source while the source exists, else it is personal. It is in no desk, has no variables, and no time to live.

ParameterRequiredDescription
snapshotYesThe id of a ready snapshot of a workspace.
toYesThe name of the new workspace. It follows the rules of a workspace name.
imageNoReplaces the frozen image.
packages (CLI --package, repeated)NoReplaces the frozen package list.

A restore counts as a new workspace against the quota of its owner. It is refused when the disk of the owner is already above diskGb.

Quota and limits

A running workspace counts its disk, 4 vCPU, and 8 GB of memory against the owner's quota. A stopped workspace counts only its disk.

ItemLimit
Name1 to 40 lowercase letters, digits, or -. Start with a letter, end with a letter or digit, no --.
Reserved namesws, env, api, admin, system, kd, bench, bld-*, and names that look like an id.
Packages100.

Tasks

Create a workspace

Reason for the task

Get a new workspace to work in.

Job set-up

Procedure

  1. Create the workspace. Add packages, repo, and branch as needed.

    Python
    from kloudlite import Kodekl = Kloudlite()ws = kl.workspaces.create(workspace="backend")print(ws["id"], ws["state"])
    TypeScript
    import { Kode } from "./index.ts";const kl = new Kloudlite();const ws: any = await kl.workspaces.create({ workspace: "backend" });console.log(ws.id, ws.state);
    Go
    kl := kloudlite.New(kloudlite.Options{})ws, err := kl.Workspaces.Create(kloudlite.CreateWorkspace{Workspace: "backend", QuotaGb: 20})if err != nil {    log.Fatal(err)}fmt.Println(ws["id"], ws["state"])
    Ruby
    require_relative "kloudlite"kl = Kloudlite::Client.newws = kl.workspaces.create(workspace: "backend")puts "#{ws['id']} #{ws['state']}"
    Java
    var kl = new Kloudlite();System.out.println(kl.workspaces().create(new Kloudlite.CreateWorkspace().workspace("backend")));
    CLI
    kode workspaces create backend

Close-out

  1. Show the status as in task 002. Wait until state is ready.

Show the status of a workspace

Reason for the task

Check whether a workspace runs, and why not.

Job set-up

Procedure

  1. Show the workspace.

    Python
    ws = kl.workspaces.get(workspace="backend")print(ws["state"], ws["packages_status"], ws["placed"], ws["degraded"])
    TypeScript
    const ws: any = await kl.workspaces.get({ workspace: "backend" });console.log(ws.state, ws.packages_status, ws.placed, ws.degraded);
    Go
    ws, err := kl.Workspaces.Get("backend")if err != nil {    log.Fatal(err)}fmt.Println(ws["state"], ws["packages_status"], ws["placed"], ws["degraded"])
    Ruby
    ws = kl.workspaces.get(workspace: "backend")puts [ws["state"], ws["packages_status"], ws["placed"], ws["degraded"]].join(" ")
    Java
    System.out.println(kl.workspaces().get("backend"));
    CLI
    kode workspaces get backend

Close-out

  1. Make sure that state is what you expect. If not, see Troubleshooting.

Start a workspace

Reason for the task

Run a stopped workspace.

Job set-up

Procedure

  1. Start the workspace.

    Python
    kl.workspaces.start(workspace="backend")
    TypeScript
    await kl.workspaces.start({ workspace: "backend" });
    Go
    if err := kl.Workspaces.Start("backend"); err != nil {    log.Fatal(err)}
    Ruby
    kl.workspaces.start(workspace: "backend")
    Java
    kl.workspaces().start("backend");
    CLI
    kode workspaces start backend

Close-out

  1. Show the status as in task 002. Wait until state is ready.

Stop a workspace

Reason for the task

Release the compute of a workspace and keep its home.

Job set-up

CAUTION

All processes end and all connections close.

Procedure

  1. Stop the workspace.

    Python
    kl.workspaces.stop(workspace="backend")
    TypeScript
    await kl.workspaces.stop({ workspace: "backend" });
    Go
    if err := kl.Workspaces.Stop("backend"); err != nil {    log.Fatal(err)}
    Ruby
    kl.workspaces.stop(workspace: "backend")
    Java
    kl.workspaces().stop("backend");
    CLI
    kode workspaces stop backend

Close-out

  1. Show the status as in task 002. Make sure that state is stopped.

Set the time to live

Reason for the task

Delete a workspace automatically after a period without use.

Job set-up

WARNING

When the time ends, the workspace and its home are deleted. Take a snapshot of work that you want to keep.

Procedure

  1. Set ttl_secs from 600 to 2592000. Set it to no value (None, null, nil) to clear it.

    Python
    kl.workspaces.set_ttl(workspace="backend", ttl_secs=86400)
    TypeScript
    await kl.workspaces.setTtl({ workspace: "backend", ttlSecs: 86400 });
    Go
    ttl := int64(86400)if _, err := kl.Workspaces.SetTTL("backend", &ttl); err != nil {    log.Fatal(err)}
    Ruby
    kl.workspaces.set_ttl(workspace: "backend", ttl_secs: 86400)
    Java
    kl.workspaces().setTtl("backend", 86400L);
    CLI
    kode workspaces set-ttl backend --ttl-secs 86400

Close-out

  1. Make sure that expires_at shows a time, or no value if you cleared it.

Delete a workspace

Reason for the task

Remove a workspace that you no longer need.

Job set-up

WARNING

The home is deleted. Work that is not in a snapshot or a repository is lost.

Procedure

  1. Delete the workspace.

    Python
    kl.workspaces.delete(workspace="backend")
    TypeScript
    await kl.workspaces.delete({ workspace: "backend" });
    Go
    if err := kl.Request("DELETE", "/v1/workspaces/backend", kloudlite.RequestOptions{}); err != nil {    log.Fatal(err)}
    Ruby
    kl.workspaces.delete(workspace: "backend")
    Java
    kl.workspaces().delete("backend");
    CLI
    kode workspaces delete backend

Close-out

  1. Make sure that state is deleted.

List workspaces

Reason for the task

Find the workspaces that you can use.

Job set-up

Procedure

  1. Show the list. Pass a team to list the workspaces of that team.

    Python
    print(kl.workspaces.list())
    TypeScript
    console.log(await kl.workspaces.list());
    Go
    list, err := kl.Workspaces.List("")if err != nil {    log.Fatal(err)}fmt.Println(list)
    Ruby
    p kl.workspaces.list
    Java
    System.out.println(kl.workspaces().list(null));
    CLI
    kode workspaces list

Close-out

  1. Make sure that each workspace that you expect is listed.

Clone a workspace

Reason for the task

Create a new workspace that starts with a copy of the files of a workspace.

Job set-up

Procedure

  1. Clone the workspace. Use a free name for to.

    Python
    new = kl.workspaces.clone(workspace="backend", to="api-experiment", task="try the new parser")print(new["id"], new["state"], new["based_on"])
    TypeScript
    const created = (await kl.workspaces.clone({ workspace: "backend", to: "api-experiment", task: "try the new parser" })) as Record<string, unknown>;console.log(created.id, created.state, created.based_on);
    Go
    created, err := kl.Workspaces.Clone(kloudlite.CloneWorkspace{Workspace: "backend", To: "api-experiment", Task: "try the new parser"})if err != nil {    log.Fatal(err)}fmt.Println(created["id"], created["state"], created["based_on"])
    Ruby
    created = kl.workspaces.clone(workspace: "backend", to: "api-experiment", task: "try the new parser")puts created.values_at("id", "state", "based_on").join(" ")
    Java
    // The SDK returns the answer as raw JSON. Read the fields with your own JSON library.String created = kl.workspaces().clone(new Kloudlite.CloneWorkspace().workspace("backend").to("api-experiment").task("try the new parser"));System.out.println(created);
    CLI
    kode workspaces clone backend --to api-experiment --task "try the new parser"

Close-out

  1. Read based_on. Write down snapshot, the copy that the clone started from.
  2. Wait until the state of the new workspace is ready.

Restore a workspace from a snapshot

Reason for the task

Create a new workspace from the state that a snapshot froze.

Job set-up

Procedure

  1. Restore the workspace. Use the id for snapshot, and a free name for to.

    Python
    new = kl.workspaces.restore(snapshot="ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to="api-restored")print(new["id"], new["state"])
    TypeScript
    const created = (await kl.workspaces.restore({ snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to: "api-restored" })) as Record<string, unknown>;console.log(created.id, created.state);
    Go
    created, err := kl.Workspaces.Restore(kloudlite.RestoreWorkspace{Snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", To: "api-restored"})if err != nil {    log.Fatal(err)}fmt.Println(created["id"], created["state"])
    Ruby
    created = kl.workspaces.restore(snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to: "api-restored")puts created.values_at("id", "state").join(" ")
    Java
    // The SDK returns the answer as raw JSON. Read the fields with your own JSON library.String created = kl.workspaces().restore(new Kloudlite.RestoreWorkspace().snapshot("ws-0a1b2c3d4e5f6a7b-8a7b6c5d").to("api-restored"));System.out.println(created);
    CLI
    kode workspaces restore --snapshot ws-0a1b2c3d4e5f6a7b-8a7b6c5d --to api-restored
  2. Add image, or packages to the call in step 1 to replace a frozen value. In the CLI, add --image, or --package.

    Python
    new = kl.workspaces.restore(snapshot="ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to="api-restored", packages=["ripgrep", "nodejs@20"])print(new["id"], new["state"])
    TypeScript
    const created = (await kl.workspaces.restore({  snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d",  to: "api-restored",  packages: ["ripgrep", "nodejs@20"],})) as Record<string, unknown>;console.log(created.id, created.state);
    Go
    created, err := kl.Workspaces.Restore(kloudlite.RestoreWorkspace{    Snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d",    To:       "api-restored",    Packages: []string{"ripgrep", "nodejs@20"},    QuotaGb:  40,})if err != nil {    log.Fatal(err)}fmt.Println(created["id"], created["state"])
    Ruby
    created = kl.workspaces.restore(snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to: "api-restored", packages: ["ripgrep", "nodejs@20"])puts created.values_at("id", "state").join(" ")
    Java
    String created = kl.workspaces().restore(new Kloudlite.RestoreWorkspace()    .snapshot("ws-0a1b2c3d4e5f6a7b-8a7b6c5d")    .to("api-restored")    .packages("ripgrep", "nodejs@20")    );System.out.println(created);
    CLI
    kode workspaces restore --snapshot ws-0a1b2c3d4e5f6a7b-8a7b6c5d --to api-restored --package ripgrep --package nodejs@20

Close-out

  1. Wait until the state of the new workspace is ready.
  2. Examine packages_status. Make sure that the packages are installed.

Troubleshooting

SymptomPossible causeCorrective action
422 with name must be 1-40 lowercase letters…The name breaks the naming rules.Use a name that follows Quota and limits.
409 with a workspace named "backend" already exists hereThe owner already has a workspace with this name.Use another name, or delete the old workspace.
409 with workspaces: 5 of 5 in use; request more under QuotaThe owner's quota is used up. The first word names the dimension, for example diskGb or cpu.Delete unused workspaces or snapshots, or request more quota.
409 with backend is managed by desk dev; use the deskThe workspace belongs to a desk, and the call starts, stops, or deletes it directly.Control it through its desk. To start it, run kode desks start.
409 with workspace backend belongs to desk dev; start the desk: kode desks start --desk devThe workspace belongs to a desk, and the call starts it directly.Start the desk instead: run kode desks start --desk dev.
400 with branch is required with reporepo is set without branch.Set branch.
409 with the storage pool is full; retry later or ask an operatorThe storage pool of the region cannot hold a new volume or copy.Wait, then try again, or ask an operator.
422 with task must be at most 200 charactersThe note in task of a clone is too long.Use a shorter note.
409 with not ready yet: no volume for this workspaceThe source of a clone has no disk yet.Wait until the source is ready, then clone again.
409 with a snapshot is already being cut for this workspaceA snapshot or a clone of the source is in progress.Wait a short time, then clone again.
404 with not found in a restoreThe snapshot does not exist, is not yours, or is not ready.Use an id with the phase ready from the lineage.
400 with this snapshot was cut from an environment; use POST /v1/environments/{id}/clone …You restored a workspace from an environment snapshot.Clone the environment from the snapshot.
state stays creating and placed is NoCapacityThe platform has no capacity right now.Wait. The workspace starts when capacity frees up.
state stays creating and packages_status is BuildingPackages are being installed.Wait. See Packages.
state is errorThe name, source repository, or snapshot is not valid.Delete the workspace and create it again with valid values.