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

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. 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. 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. 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. Key methods include openWorkspace, newWorkspace, and deleteWorkspace. The frontend consumes these via hooks such as useWorkspaceOpen in 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:

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:

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:

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.

Reading Files in the Backend

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

// 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:

// 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 remains consistent with the actual file system state.

Summary

  • WorkspaceGitCache in 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 owns workspace instances, managing their lifecycle from creation to deletion via newUniqueId() and namespace APIs.
  • OverseerDurableObject in 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, 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 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 are synchronous within the Object's event loop, concurrent writes to the same workspace are processed sequentially, eliminating race conditions on the Git tree.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →