# How the Workspace is Managed as a Durable Object in Cloudflare OS

> Discover how Cloudflare OS manages the workspace as a Durable Object, the WorkspaceGitCache class, ensuring strongly consistent, edge durable storage for Git state and output indexes.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: internals
- Published: 2026-09-05

---

**Cloudflare OS persists each workspace as a dedicated Durable Object—the `WorkspaceGitCache` class—providing strongly-consistent, edge-durable storage for Git state and output indexes.**

Cloudflare OS (the `cloudflare/cloudflare-os` repository) treats user workspaces as first-class Durable Objects rather than ephemeral session data. This architecture guarantees that a workspace’s Git history, file contents, and generated outputs survive worker restarts and remain accessible with single-millisecond latency from any edge location.

## Core Architecture

The workspace management system relies on a hierarchy of three Durable Object classes that handle persistence, ownership, and orchestration.

### WorkspaceGitCache: The Workspace State Machine

At the center of the system sits **`WorkspaceGitCache`**, defined in [`packages/workshop-backend/src/git-cache.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/git-cache.ts). This class extends `DurableObject` and implements the actual workspace behavior. It maintains:

- **`gitCache`**: A low-level Git representation (blobs, trees, commits) stored in `DurableObjectStorage`.
- **`outputs`**: An index mapping gadget IDs to output entries for fast retrieval of generated files.

All reads and writes to a workspace flow through this class, ensuring atomicity and strong consistency. The constructor receives the standard `DurableObjectState` and `Env`, exposing methods like `readFileAtCommitIfExists` and `writeFile` that operate directly on the Durable Object's transactional storage.

### UserDurableObject: Workspace Ownership

Individual workspaces do not float in isolation; they are owned by a **`UserDurableObject`** implemented in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts). This Durable Object maintains a Map of workspace IDs to `WorkspaceGitCache` stubs. It provides lifecycle methods:

- `newWorkspace()`: Generates a fresh Durable Object ID via `env.WorkspaceNamespace.newUniqueId()` and instantiates a new `WorkspaceGitCache`.
- `getWorkspace(id)`: Returns a stub to an existing workspace.
- `deleteWorkspace(id)`: Removes the workspace state and calls `env.WorkspaceNamespace.delete(id)`.

By mediating access through `UserDurableObject`, the system enforces per-user isolation and quota limits.

### OverseerDurableObject: Cross-Workspace Orchestration

Higher-level operations that span multiple workspaces or integrate with chat systems are handled by the **`OverseerDurableObject`** in [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts). This orchestrator receives RPC calls for actions like creating gadgets or syncing outputs, resolves the target user, and forwards requests to the appropriate `UserDurableObject` and subsequently to the `WorkspaceGitCache`.

### RPC API Surface

The frontend communicates with these Durable Objects through Cap’n Web RPC definitions in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts). Key methods include `openWorkspace`, `newWorkspace`, and `deleteWorkspace`. The frontend consumes these via hooks such as `useWorkspaceOpen` in [`packages/workshop-frontend/src/useWorkspaceOpen.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/useWorkspaceOpen.ts), which handles the Durable Object stub initialization and error states like `workspaceNotFound`.

## Workspace Lifecycle

Understanding how a workspace moves from creation to deletion clarifies the Durable Object boundaries.

### 1. Creation and ID Generation

When a user triggers workspace creation, the request hits the `OverseerDurableObject`, which delegates to the user's `UserDurableObject`. Inside `newWorkspace()`, the system calls:

```typescript
const id = env.WorkspaceNamespace.newUniqueId();
const stub = env.WorkspaceNamespace.get(id);

```

This generates a globally unique Durable Object ID that serves as the canonical workspace identifier.

### 2. State Persistence

Once instantiated, `WorkspaceGitCache` stores data via `this.storage` (the `DurableObjectStorage` interface). The Git data and output index are kept in memory during active use and flushed to the underlying edge storage automatically by the runtime, surviving process restarts.

### 3. Access Patterns

Frontend code obtains a direct stub to a workspace through the RPC layer. The stub points to the specific `WorkspaceGitCache` instance, allowing methods like:

```typescript
const content = await rpc.workspace(id).readFileAtCommit(commit, path);

```

This RPC is serialized by the Durable Object runtime, preventing concurrent write conflicts on the Git state.

### 4. Output Synchronization

When a gadget writes generated files, the `WorkspaceGitCache` updates its internal `outputs` index. The `OverseerDurableObject` then propagates these changes to subscribed chats or UI components, ensuring the output index remains coherent across the distributed system.

### 5. Deletion and Cleanup

Deleting a workspace invokes `UserDurableObject.deleteWorkspace()`, which clears the local ID mapping and calls the Durable Object namespace deletion API to purge the persistent state associated with that workspace ID.

## Code Examples

### Creating a Workspace from the Frontend

The following TypeScript React snippet demonstrates how the frontend initiates workspace creation via RPC:

```typescript
import { useWorkspaceOpen } from "./hooks/useWorkspaceOpen";

function CreateWorkspaceButton() {
  const { openWorkspace } = useWorkspaceOpen();

  const handleCreate = async () => {
    // Calls the newWorkspace RPC defined in api.ts
    const workspace = await rpc.newWorkspace({ title: "Edge Functions" });
    // workspace.id is the Durable Object ID
    await openWorkspace(workspace.id);
  };

  return <button onClick={handleCreate}>New Workspace</button>;
}

```

*This flow eventually triggers `env.WorkspaceNamespace.newUniqueId()` inside [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts).*

### Reading Files in the Backend

Inside `WorkspaceGitCache`, file reads resolve against the Git state stored in `DurableObjectStorage`:

```typescript
// packages/workshop-backend/src/git-cache.ts
async function readFile(path: string, commit?: string) {
  const commitRef = commit ?? await this.gitCache.resolveHead();
  const content = await this.gitCache.readFileAtCommitIfExists(commitRef, path);
  if (content === null) {
    throw new Error(`File not found: ${path}`);
  }
  return content;
}

```

*The `readFileAtCommitIfExists` method operates directly on the `gitCache` instance backed by `this.storage`.*

### Synchronizing Workspace Outputs

After a gadget generates artifacts, the backend updates the workspace output index:

```typescript
// Called within OverseerDurableObject or UserDurableObject
async function syncWorkspaceOutputs(
  workspaceId: string, 
  entries: WorkspaceOutputEntry[]
) {
  const ws = this.storage.outputs.byWorkspace.get(workspaceId);
  if (!ws) throw new Error("Workspace not found");
  
  ws.deleteAll(); // Clear stale entries
  for (const entry of entries) {
    ws.put(entry.id, entry);
  }
}

```

*This pattern ensures the `outputs` index in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) remains consistent with the actual file system state.*

## Summary

- **WorkspaceGitCache** in [`packages/workshop-backend/src/git-cache.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/git-cache.ts) is the Durable Object class that hosts each workspace's Git state and output index.
- **UserDurableObject** in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) owns workspace instances, managing their lifecycle from creation to deletion via `newUniqueId()` and namespace APIs.
- **OverseerDurableObject** in [`packages/workshop-backend/src/overseer.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/overseer.ts) orchestrates cross-workspace operations and forwards RPC calls to the appropriate user and workspace objects.
- The system uses **strongly-consistent Durable Object storage** to guarantee atomic Git operations and automatic persistence across edge nodes.
- Frontend interaction occurs through Cap’n Web RPC defined in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts), abstracted by hooks like `useWorkspaceOpen`.

## Frequently Asked Questions

### How does Cloudflare OS guarantee workspace data survives a worker restart?

Each workspace runs as a `WorkspaceGitCache` Durable Object that writes state to `DurableObjectStorage`. This storage is backed by Cloudflare's edge infrastructure and automatically persists to disk, ensuring that Git trees, file contents, and output indexes are restored when the Durable Object is re-instantiated on a different node.

### Can two users access the same workspace simultaneously?

No. Workspaces are isolated per user through the `UserDurableObject` ownership layer. While the underlying `WorkspaceGitCache` could theoretically be accessed by any actor holding its ID, the `UserDurableObject` in [`packages/workshop-backend/src/user.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/user.ts) controls the ID mapping and only exposes workspaces belonging to the authenticated user, preventing cross-tenant access.

### What happens when a workspace is deleted?

Deletion flows through `UserDurableObject.deleteWorkspace()`, which removes the ID from the user's internal map and calls `env.WorkspaceNamespace.delete(id)` to purge the Durable Object's persistent storage. This permanently clears the Git cache and output index associated with that workspace ID.

### How are concurrent file writes prevented?

The Durable Object runtime serializes all RPC calls to a specific `WorkspaceGitCache` instance. Because the Git operations in [`packages/workshop-backend/src/git-cache.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/git-cache.ts) are synchronous within the Object's event loop, concurrent writes to the same workspace are processed sequentially, eliminating race conditions on the Git tree.