Workspaces
Create, start, stop, and delete workspaces, the development machines that you work in.
Last updated
On this page
- Overview
- Workspace and capsule
- Workspace types
- Creation parameters
- States
- Lifecycle
- Clone
- Restore
- Quota and limits
- Tasks
- Create a workspace
- Show the status of a workspace
- Start a workspace
- Stop a workspace
- Set the time to live
- Delete a workspace
- List workspaces
- Clone a workspace
- Restore a workspace from a snapshot
- Troubleshooting
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-.
| Part | Description |
|---|---|
| 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. |
| Packages | The toolchain, from nixpkgs. See Packages. |
| Tool server | The interface for programs: files, commands, and processes on port 7788. |
| SSH | The interface for terminals and editors. See Connection. |
| Resources | Up 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.
| Aspect | Workspace | Capsule |
|---|---|---|
| Use | Long work: you edit, run, and test code. | One command or one shell. |
| Lifetime | Until you delete it. It stops and starts again. | Until its main process exits. |
| Files | Kept while it is stopped. | Gone at the end. |
| Access | Tool 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.
| Aspect | Standalone | Managed |
|---|---|---|
| Created with | POST /v1/workspaces | POST /v1/desks/{id}/workspaces. See Desks. |
| Started, stopped, and deleted by | You, directly | Its desk |
| Reaches an environment | No | The desk's environment |
| Deleted | When you delete it | When you delete it in the desk, or when the desk is deleted |
desk field | Empty | The 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
| Parameter | Required | Description |
|---|---|---|
workspace | Yes | The name. Use team/name for a team workspace. |
repo and branch | No | A repository (owner/name) and a branch to clone into the workspace. Set both. |
packages | No | The packages to install. See Packages. |
env | No | Variables. 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_secs | No | The time to live in seconds. See Lifecycle. |
States
state | Meaning |
|---|---|
creating | Not running yet: waiting for capacity, installing packages, or restarting on another node after its node was lost (placement is null). |
ready | Running and reachable. |
stopped | Not running. The home is kept. |
error | The definition is not valid. |
deleted | Being 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
deskfield 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 takes | A clone does not take |
|---|---|
| The files of the home | The variables |
image, packages, and locks | The 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.
| Field | Description |
|---|---|
snapshot | The copy that the clone started from. |
at | The time of that copy. Absent for a copy that this request made. |
age_seconds | The age of the copy in seconds. Always 0, because the platform makes a new copy for each clone. |
interrupted | Always 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.
| Parameter | Required | Description |
|---|---|---|
to | Yes | The name of the new workspace. It follows the rules of a workspace name, and is unique in the team of the source. |
task | No | A 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.
| Parameter | Required | Description |
|---|---|---|
snapshot | Yes | The id of a ready snapshot of a workspace. |
to | Yes | The name of the new workspace. It follows the rules of a workspace name. |
image | No | Replaces the frozen image. |
packages (CLI --package, repeated) | No | Replaces 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.
| Item | Limit |
|---|---|
| Name | 1 to 40 lowercase letters, digits, or -. Start with a letter, end with a letter or digit, no --. |
| Reserved names | ws, env, api, admin, system, kd, bench, bld-*, and names that look like an id. |
| Packages | 100. |
Tasks
Create a workspace
Reason for the task
Get a new workspace to work in.
Job set-up
- Access: you. A team workspace needs team membership.
- References:
POST /v1/workspaces.
Procedure
Create the workspace. Add
packages,repo, andbranchas needed.from kloudlite import Kodekl = Kloudlite()ws = kl.workspaces.create(workspace="backend")print(ws["id"], ws["state"])import { Kode } from "./index.ts";const kl = new Kloudlite();const ws: any = await kl.workspaces.create({ workspace: "backend" });console.log(ws.id, ws.state);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"])require_relative "kloudlite"kl = Kloudlite::Client.newws = kl.workspaces.create(workspace: "backend")puts "#{ws['id']} #{ws['state']}"var kl = new Kloudlite();System.out.println(kl.workspaces().create(new Kloudlite.CreateWorkspace().workspace("backend")));kode workspaces create backend
Close-out
- Show the status as in task 002. Wait until
stateisready.
Show the status of a workspace
Reason for the task
Check whether a workspace runs, and why not.
Job set-up
- Access: you.
- References:
GET /v1/workspaces/{id}.
Procedure
Show the workspace.
ws = kl.workspaces.get(workspace="backend")print(ws["state"], ws["packages_status"], ws["placed"], ws["degraded"])const ws: any = await kl.workspaces.get({ workspace: "backend" });console.log(ws.state, ws.packages_status, ws.placed, ws.degraded);ws, err := kl.Workspaces.Get("backend")if err != nil { log.Fatal(err)}fmt.Println(ws["state"], ws["packages_status"], ws["placed"], ws["degraded"])ws = kl.workspaces.get(workspace: "backend")puts [ws["state"], ws["packages_status"], ws["placed"], ws["degraded"]].join(" ")System.out.println(kl.workspaces().get("backend"));kode workspaces get backend
Close-out
- Make sure that
stateis what you expect. If not, see Troubleshooting.
Start a workspace
Reason for the task
Run a stopped workspace.
Job set-up
- Access: you.
- References:
POST /v1/workspaces/{id}/start.
Procedure
Start the workspace.
kl.workspaces.start(workspace="backend")await kl.workspaces.start({ workspace: "backend" });if err := kl.Workspaces.Start("backend"); err != nil { log.Fatal(err)}kl.workspaces.start(workspace: "backend")kl.workspaces().start("backend");kode workspaces start backend
Close-out
- Show the status as in task 002. Wait until
stateisready.
Stop a workspace
Reason for the task
Release the compute of a workspace and keep its home.
Job set-up
- Access: you.
- References:
POST /v1/workspaces/{id}/stop.
All processes end and all connections close.
Procedure
Stop the workspace.
kl.workspaces.stop(workspace="backend")await kl.workspaces.stop({ workspace: "backend" });if err := kl.Workspaces.Stop("backend"); err != nil { log.Fatal(err)}kl.workspaces.stop(workspace: "backend")kl.workspaces().stop("backend");kode workspaces stop backend
Close-out
- Show the status as in task 002. Make sure that
stateisstopped.
Set the time to live
Reason for the task
Delete a workspace automatically after a period without use.
Job set-up
- Access: you.
- References:
PUT /v1/workspaces/{id}/ttl.
When the time ends, the workspace and its home are deleted. Take a snapshot of work that you want to keep.
Procedure
Set
ttl_secsfrom 600 to 2592000. Set it to no value (None,null,nil) to clear it.kl.workspaces.set_ttl(workspace="backend", ttl_secs=86400)await kl.workspaces.setTtl({ workspace: "backend", ttlSecs: 86400 });ttl := int64(86400)if _, err := kl.Workspaces.SetTTL("backend", &ttl); err != nil { log.Fatal(err)}kl.workspaces.set_ttl(workspace: "backend", ttl_secs: 86400)kl.workspaces().setTtl("backend", 86400L);kode workspaces set-ttl backend --ttl-secs 86400
Close-out
- Make sure that
expires_atshows 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
- Access: you.
- References:
DELETE /v1/workspaces/{id}, Take a snapshot of a workspace.
The home is deleted. Work that is not in a snapshot or a repository is lost.
Procedure
Delete the workspace.
kl.workspaces.delete(workspace="backend")await kl.workspaces.delete({ workspace: "backend" });if err := kl.Request("DELETE", "/v1/workspaces/backend", kloudlite.RequestOptions{}); err != nil { log.Fatal(err)}kl.workspaces.delete(workspace: "backend")kl.workspaces().delete("backend");kode workspaces delete backend
Close-out
- Make sure that
stateisdeleted.
List workspaces
Reason for the task
Find the workspaces that you can use.
Job set-up
- Access: you.
- References:
GET /v1/workspaces,kode workspaces list.
Procedure
Show the list. Pass a team to list the workspaces of that team.
print(kl.workspaces.list())console.log(await kl.workspaces.list());list, err := kl.Workspaces.List("")if err != nil { log.Fatal(err)}fmt.Println(list)p kl.workspaces.listSystem.out.println(kl.workspaces().list(null));kode workspaces list
Close-out
- 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
- Access: you. A team workspace needs team membership.
- References:
POST /v1/workspaces/{id}/clone.
Procedure
Clone the workspace. Use a free name for
to.new = kl.workspaces.clone(workspace="backend", to="api-experiment", task="try the new parser")print(new["id"], new["state"], new["based_on"])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);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"])created = kl.workspaces.clone(workspace: "backend", to: "api-experiment", task: "try the new parser")puts created.values_at("id", "state", "based_on").join(" ")// 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);kode workspaces clone backend --to api-experiment --task "try the new parser"
Close-out
- Read
based_on. Write downsnapshot, the copy that the clone started from. - Wait until the
stateof the new workspace isready.
Restore a workspace from a snapshot
Reason for the task
Create a new workspace from the state that a snapshot froze.
Job set-up
- Access: you. A team snapshot needs team membership.
- Conditions: you know the
idof areadysnapshot of a workspace. See List the lineage. - References:
POST /v1/workspaces/restore.
Procedure
Restore the workspace. Use the
idforsnapshot, and a free name forto.new = kl.workspaces.restore(snapshot="ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to="api-restored")print(new["id"], new["state"])const created = (await kl.workspaces.restore({ snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to: "api-restored" })) as Record<string, unknown>;console.log(created.id, created.state);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"])created = kl.workspaces.restore(snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to: "api-restored")puts created.values_at("id", "state").join(" ")// 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);kode workspaces restore --snapshot ws-0a1b2c3d4e5f6a7b-8a7b6c5d --to api-restoredAdd
image, orpackagesto the call in step 1 to replace a frozen value. In the CLI, add--image, or--package.new = kl.workspaces.restore(snapshot="ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to="api-restored", packages=["ripgrep", "nodejs@20"])print(new["id"], new["state"])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);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"])created = kl.workspaces.restore(snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d", to: "api-restored", packages: ["ripgrep", "nodejs@20"])puts created.values_at("id", "state").join(" ")String created = kl.workspaces().restore(new Kloudlite.RestoreWorkspace() .snapshot("ws-0a1b2c3d4e5f6a7b-8a7b6c5d") .to("api-restored") .packages("ripgrep", "nodejs@20") );System.out.println(created);kode workspaces restore --snapshot ws-0a1b2c3d4e5f6a7b-8a7b6c5d --to api-restored --package ripgrep --package nodejs@20
Close-out
- Wait until the
stateof the new workspace isready. - Examine
packages_status. Make sure that the packages are installed.
Troubleshooting
| Symptom | Possible cause | Corrective 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 here | The 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 Quota | The 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 desk | The 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 dev | The 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 repo | repo is set without branch. | Set branch. |
409 with the storage pool is full; retry later or ask an operator | The 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 characters | The note in task of a clone is too long. | Use a shorter note. |
409 with not ready yet: no volume for this workspace | The 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 workspace | A snapshot or a clone of the source is in progress. | Wait a short time, then clone again. |
404 with not found in a restore | The 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 NoCapacity | The platform has no capacity right now. | Wait. The workspace starts when capacity frees up. |
state stays creating and packages_status is Building | Packages are being installed. | Wait. See Packages. |
state is error | The name, source repository, or snapshot is not valid. | Delete the workspace and create it again with valid values. |