# Workspaces
Source: https://dorkos.ai/docs/guides/workspaces

DorkOS gives every task its own folder on disk, so agents never step on each other's files





# Workspaces [#workspaces]

DorkOS gives every task its own private copy of your repo, automatically. Start a session on issue `DOR-84` and a second one on `DOR-91`, and each gets its own folder, its own dev-server ports, and its own uncommitted changes. Neither can step on the other.

That folder is called a **workspace**. Workspaces are on by default: the first time a session works on a specific issue or spec, DorkOS provisions one behind the scenes. If you never touch this feature, nothing changes, sessions run in whatever folder they always did.

## Quick look: what you'll see [#quick-look-what-youll-see]

Open **Workspaces** from the sidebar and you'll see a card per checkout: which project it belongs to, its status, whether it has uncommitted changes, and which sessions are currently using it. While you work, the session's status bar shows a small chip like `⎇ DOR-84 · core · ● 3 changes`, so you always know which workspace a session is sitting in. Both are covered in detail below.

## Why workspaces exist [#why-workspaces-exist]

If two agents edit the same folder at the same time, bad things happen: racing commits, half-staged files, a dev server rebuilding out from under the other agent. Nobody wants to debug that. The fix is **one checkout, one writer**: give every task its own folder.

Doing that by hand means creating a `git worktree` (a second working copy of your repo that shares history with the original, so you're not re-cloning gigabytes for every task), picking a dev-server port that isn't already taken, and remembering to clean it up without losing uncommitted work. Workspaces automate all of it:

* **Isolation**: each task gets its own checkout, so parallel sessions never share a folder.
* **Collision-free ports**: the server hands each workspace its own block of ports, so two workspaces can both run a dev server at once.
* **Dirty-safe cleanup**: removing a workspace refuses to delete unsaved work unless you explicitly say to.
* **Visibility**: the Workspaces page and the session status bar show you what exists and where each session is working.

<Callout type="info">
  A workspace is just a folder. A session is "in" a workspace when its working directory (the folder
  it's running commands in) points at one, nothing else about the session changes.
</Callout>

## The /workspaces page [#the-workspaces-page]

Open **Workspaces** from the sidebar to see every managed checkout, grouped by project. Each card shows the workspace's identity, status, provider, its primary port, and whether it has uncommitted changes or is pinned. Below the divider, the card lists the sessions currently attached to it, click one to jump straight into that session, opened in the workspace's checkout.

Each card has two actions:

* **Pin / Unpin**: protect the workspace from automatic cleanup.
* **Remove**: delete the checkout. This is dirty-safe (see [Pinning and cleanup](#pinning-and-cleanup) below), so a stray click can't destroy work you haven't saved.

<Callout type="info">
  The page answers "what workspaces exist for this work, and which sessions are using them?", not
  "show me every directory on disk." It's scoped to what DorkOS manages.
</Callout>

## Knowing which workspace you're in [#knowing-which-workspace-youre-in]

When a session's working directory is a managed workspace, the **Git Status** item in the session status bar leads with the workspace identity instead of a bare branch name:

```
⎇ DOR-84 · core · ● 3 changes
```

That reads as: you're in the `DOR-84` workspace of the `core` project, with 3 uncommitted changes. (Yes, `DOR-84` is a real ticket. We used it while building this feature and never got around to swapping in a placeholder.) Hovering the chip reveals the branch, provider, allocated ports, and pinned state. If the session is in an ordinary folder instead of a managed workspace, the item just shows the normal branch chip, nothing changes.

<Callout type="info">
  The Git Status item is shown on desktop-width layouts. If you don't see it, enable it from the
  status-bar configuration popover.
</Callout>

***

## Reference [#reference]

Most people never need what's below. This is the developer-facing tail, for anyone scripting against the API or tuning `config.json`.

### Anatomy of a workspace [#anatomy-of-a-workspace]

Every workspace has a stable identity and a provisioned checkout:

<TypeTable
  type="{
  projectKey: {
    type: 'string',
    description:
      'The repository this workspace belongs to (e.g. &#x22;core&#x22;). Workspaces are grouped by project.',
  },
  key: {
    type: 'string',
    description:
      'The unit of work, typically an issue id or spec slug (e.g. &#x22;DOR-84&#x22;). Unique within a project.',
  },
  path: {
    type: 'string',
    description:
      'Absolute path to the isolated checkout on disk. This is what a session uses as its cwd.',
  },
  provider: {
    type: &#x22;'worktree' | 'clone'&#x22;,
    description:
      'How the checkout was created: a git worktree (default, fast, shared object store) or a full clone.',
  },
  branch: {
    type: 'string',
    description: 'The branch checked out in the workspace (e.g. &#x22;dork/DOR-84&#x22;).',
  },
  status: {
    type: &#x22;'provisioning' | 'ready' | 'failed' | 'removing'&#x22;,
    description: 'Lifecycle state, surfaced as a colored dot in the UI.',
  },
  portBase: {
    type: 'number',
    description:
      'First port of this workspace’s reserved block. Ports derive from this (see Ports below).',
  },
  pinned: {
    type: 'boolean',
    description: 'When true, the workspace is exempt from automatic cleanup.',
    default: 'false',
  },
}"
/>

The `projectKey` + `key` pair is unique, so asking for the same unit of work twice returns the **same** workspace rather than provisioning a duplicate.

### Binding a session to a workspace [#binding-a-session-to-a-workspace]

Binding is opt-in, and most people never do it by hand, the DorkOS client sends this automatically when you start work on an issue or spec through the UI. It's shown here as the reference form, for anyone calling the API directly.

When you send a message to a session, include a `workspaceKey` (and optionally a `workspaceProvider`) and the server will ensure the workspace exists, provisioning it on first use, then run the turn with the workspace's checkout as the working directory and its port block injected into the environment:

```bash
curl -X POST http://localhost:4242/api/sessions/{sessionId}/messages \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Start on the workspace manager",
    "workspaceKey": "DOR-84",
    "workspaceProvider": "worktree"
  }'
```

Omit `workspaceKey` and the session runs in whatever `cwd` it already had, the path is byte-for-byte unchanged. If the WorkspaceManager is disabled, or fails to provision for any reason, the turn falls back to the supplied `cwd` instead of erroring, so binding can never break a session.

### Ports [#ports]

Each workspace reserves a **contiguous block** of ports so multiple workspaces can run dev servers simultaneously without clashing. Ports derive from the workspace's `portBase`:

<TypeTable
  type="{
  DORKOS_PORT: { type: 'portBase + 0', description: 'The DorkOS API server for this workspace' },
  VITE_PORT: { type: 'portBase + 1', description: 'The Vite client dev server' },
  SITE_PORT: { type: 'portBase + 2', description: 'The marketing/docs site dev server' },
}"
/>

The server allocates the **lowest free block**, so two workspaces are guaranteed disjoint ports. With the default `portBase` of `4250` and a block size of `10`, the first workspace gets `4250/4251/4252`, the next `4260/4261/4262`, and so on. The allocated values are written into the workspace's `.env` file, so a dev server started inside the checkout picks them up automatically, no manual port-hunting required.

### Pinning and cleanup [#pinning-and-cleanup]

Removing a workspace deletes its checkout, so it's deliberately conservative:

* **Dirty-safe removal.** Remove first checks the checkout for uncommitted, untracked, or unpushed work. If any exists, the request is refused and the UI re-prompts with an explicit force confirmation rather than silently destroying work, so you can't fat-finger delete on something you haven't saved. Forcing deletes it permanently.
* **Pinning.** A pinned workspace is exempt from automatic cleanup. Pin a workspace you want to keep around regardless of retention policy; the pin persists and shows on the card and in the status-bar chip tooltip.

<Callout type="warn">
  Pinning protects against *automatic* cleanup, not against an explicit remove. The two guards are
  independent: dirty-state protects unsaved work; pinning protects from reclamation policy.
</Callout>

### Providers [#providers]

A workspace's `provider` determines how its checkout is created:

| Provider     | How it's created                                                  | When to use it                                                        |
| ------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------- |
| **worktree** | A `git worktree` off the existing repo (shares the object store). | Default. Fast, space-efficient, ideal for branches of the same repo.  |
| **clone**    | A full `git clone` into the workspace path.                       | When you need a fully independent copy rather than a linked worktree. |

The default provider is configurable (`workspace.defaultProvider`); callers can override it per-request with `workspaceProvider`.

### Configuration Reference [#configuration-reference]

Workspaces are configured under `workspace` in `~/.dork/config.json`:

<TypeTable
  type="{
  enabled: {
    type: 'boolean',
    description:
      'Whether the WorkspaceManager is active (binding sessions, allocating ports). Disable to fall back to plain cwd behavior.',
    default: 'true',
  },
  rootPath: {
    type: 'string | null',
    description: 'Where workspace checkouts live. null resolves to <dorkHome>/workspaces.',
    default: 'null',
  },
  portBase: {
    type: 'number',
    description: 'First port of the allocation pool.',
    default: '4250',
  },
  portBlockSize: {
    type: 'number',
    description: 'How many contiguous ports each workspace reserves (≥3, for DORKOS/VITE/SITE).',
    default: '10',
  },
  defaultProvider: {
    type: &#x22;'worktree' | 'clone'&#x22;,
    description: 'Provider used when a caller does not specify one.',
    default: &#x22;'worktree'&#x22;,
  },
  retentionCap: {
    type: 'number | null',
    description: 'Optional cap on retained workspaces. null disables the age/cap sweep.',
    default: 'null',
  },
}"
/>

### API Reference [#api-reference]

All endpoints are under `/api/workspaces`:

| Method & path                  | Purpose                                                                                   |
| ------------------------------ | ----------------------------------------------------------------------------------------- |
| `GET /api/workspaces`          | List workspaces (optionally `?projectKey=`), each with attached sessions and dirty state. |
| `GET /api/workspaces/resolve`  | Resolve a workspace from an absolute `?path=`: powers the session status-bar indicator.   |
| `POST /api/workspaces/ports`   | Preview the port block a given checkout `path` would receive.                             |
| `POST /api/workspaces`         | Ensure a workspace (`{ projectKey, key, source, provider? }`): provisions on first use.   |
| `GET /api/workspaces/:id`      | Fetch a single workspace.                                                                 |
| `POST /api/workspaces/:id/pin` | Pin or unpin (`{ pinned: boolean }`).                                                     |
| `DELETE /api/workspaces/:id`   | Remove (`?force=true` to override a dirty refusal).                                       |

<Callout type="info">
  `DELETE` returns `200` with `{ removed: false, blocked: 'dirty' }` when it refuses a dirty
  workspace (not a `4xx`), so clients can distinguish "blocked, ask to force" from a real error.
  A `404` means the workspace genuinely doesn't exist.
</Callout>

## Next Steps [#next-steps]

<Cards>
  <Card title="Sessions" href="/docs/concepts/sessions">
    How DorkOS sessions work (the thing a workspace binds to).
  </Card>

  <Card title="Agent Identity" href="/docs/guides/agents">
    Give your projects a name, color, and emoji so you think in agents, not file paths.
  </Card>

  <Card title="Agent Coordination" href="/docs/guides/agent-coordination">
    Run multiple agents in parallel, where per-workspace isolation earns its keep.
  </Card>
</Cards>
