How to Extend and Customize Runtime Types in Cloudflare Computer

Yes—you can extend and customize runtime types in Cloudflare Computer by implementing the WorkspaceModuleBackend interface and registering your backend with the Workspace constructor.

Cloudflare Computer exposes a pluggable runtime architecture that lets you swap or augment execution backends without modifying core code. The workspace.runtime surface acts as a thin router that delegates calls to registered backends, making it possible to add custom execution environments, new protocols, or extended type systems.


Understanding the Runtime Extension Architecture

The runtime layer in Cloudflare Computer separates routing logic from execution logic. In packages/computer/src/workspace.ts, the Workspace class accepts a backends array that defines which execution engines are available. The router resolves backend IDs at runtime and forwards runtime.exec, runtime.getExec, and related calls to the appropriate implementation.

This design means you can:

  • Add entirely new execution backends (custom containers, WASM modules, remote sandboxes)
  • Replace built-in backends with specialized implementations
  • Extend the type system for return values and event payloads

The Backend Registration Mechanism

When constructing a Workspace, you provide custom backends via the backends option:

import { Workspace } from "@cloudflare/computer";

const ws = new Workspace({
  storage: makeStorage(),
  backends: [myCustomBackend],     // Register your implementation
  defaultBackendId: "my-backend",  // Optional: make it the default
});

The backends parameter accepts WorkspaceRegisteredBackend objects as defined in packages/computer/src/workspace.ts. The router uses these registrations to:

  1. Match the backend option passed to runtime.exec() calls
  2. Fall back to defaultBackendId or the first registered backend when unspecified

Implementing the WorkspaceModuleBackend Interface

Every custom backend must conform to the WorkspaceModuleBackend contract in packages/computer/src/runtime/types.ts. The required shape includes:

Property Type Description
protocol "module" Fixed discriminator for module-type backends
id string Unique identifier used for routing
type string Human-readable type name
connect(host) Promise<WorkspaceModuleBackendHandle> Factory that returns an execution handle

The connect method receives a host context and must return a WorkspaceModuleBackendHandle implementing the execution primitives.


Building a Custom Backend Handle

The handle returned by connect must implement four core methods plus an optional close method, as specified in packages/computer/src/runtime/types.ts:

  • exec(input) — Start a new execution, return a ModuleExecutionEnvelope with an event stream
  • getExec(id) — Reconnect to an existing execution (optional for stateless backends)
  • killExec(id) — Terminate a running execution
  • disposeExec(id) — Clean up resources for a completed execution
  • close() — Release global backend resources (optional)

The event stream must follow the WorkspaceRuntimeExecHandle semantics: ordered events with id, seq, name, and value fields, terminating with an exit event.

Complete Custom Backend Example

// echo-backend.ts
import type {
  WorkspaceModuleBackend,
  WorkspaceModuleBackendHandle,
  ModuleExecutionInput,
  ModuleExecutionEnvelope,
} from "@cloudflare/computer";

export const echoBackend: WorkspaceModuleBackend = {
  protocol: "module",
  id: "echo",
  type: "demo-echo",
  
  async connect(host) {
    const handle: WorkspaceModuleBackendHandle = {
      async exec(input: ModuleExecutionInput): Promise<ModuleExecutionEnvelope> {
        const resultId = input.id ?? crypto.randomUUID();
        
        return {
          id: resultId,
          runtimeId: undefined,
          events: new ReadableStream({
            start(controller) {
              // Echo the input as stdout
              const payload = typeof input.source === "string" ? input.source : "";
              
              controller.enqueue({
                id: resultId,
                seq: 0,
                name: "stdout",
                value: payload,
              });
              
              controller.enqueue({
                id: resultId,
                seq: 1,
                name: "exit",
                code: 0,
              });
              
              controller.close();
            },
          }),
        };
      },

      async getExec() {
        throw new Error("Echo backend does not support getExec");
      },
      
      async killExec() {
        // No-op: executions complete immediately
      },
      
      async disposeExec() {
        // No-op: nothing to dispose
      },
      
      async close() {
        // Clean up any global resources
      },
    };
    
    return handle;
  },
};

Registering and Using Your Custom Runtime

With the backend defined, wire it into your workspace and invoke it through the standard runtime.exec API:

import { Workspace } from "@cloudflare/computer";
import { echoBackend } from "./echo-backend";

const ws = new Workspace({
  storage: makeStorage(),
  backends: [echoBackend],
});

// Execute using the custom backend
const handle = await ws.runtime.exec("Hello, Cloudflare Computer!", {
  backend: "echo",      // Select by backend ID
  encoding: "utf8",
});

// Consume the event stream
for await (const event of handle) {
  if (event.name === "stdout") {
    console.log("Received:", event.value);
  }
}

const result = await handle.result();
console.log("Exit code:", result.exitCode);  // 0

The backend option in runtime.exec selects your registered implementation. Omit it to use the defaultBackendId or first available backend.


Extending Runtime Value Types

To customize the type system for values flowing through your backend, extend the WorkspaceRuntimeValue union in packages/computer/src/runtime/types.ts and re-export from your package:

// custom-types.ts
import type { WorkspaceRuntimeValue } from "@cloudflare/computer";

export type ExtendedRuntimeValue = 
  | WorkspaceRuntimeValue 
  | { __tag: "custom-struct"; payload: Record<string, unknown> };

Consumers importing your extended types gain type safety for backend-specific return values while maintaining compatibility with the base runtime API.


Key Implementation Files

File Purpose
packages/computer/src/runtime/types.ts Defines WorkspaceModuleBackend, WorkspaceModuleBackendHandle, and WorkspaceRuntimeValue—the core contracts for customization
packages/computer/src/workspace.ts Implements backend registration and routing logic in the Workspace class
packages/computer/src/runtime/wire.ts Low-level event stream protocol used by all backends
docs/05_runtime_interface.md Public API documentation for workspace.runtime
docs/16_code_execution.md Usage patterns and backend selection semantics

Summary

  • Backend registration happens at Workspace construction via the backends array in packages/computer/src/workspace.ts
  • The WorkspaceModuleBackend interface in packages/computer/src/runtime/types.ts defines the contract for custom runtimes
  • Four core methods (exec, getExec, killExec, disposeExec) plus optional close must be implemented
  • Event streams must conform to WorkspaceRuntimeExecHandle semantics with ordered, named events terminating in exit
  • Type extensions are achieved by augmenting WorkspaceRuntimeValue and re-exporting from your package
  • Runtime selection uses the backend option in runtime.exec calls, routed through the internal backend registry

Frequently Asked Questions

How do I make my custom backend the default runtime?

Pass defaultBackendId matching your backend's id in the Workspace constructor. When runtime.exec is called without a backend option, the router selects this default.

Can I override the built-in container or bash backends?

Yes—register a backend with the same id as a built-in (e.g., "container", "bash") and it will take precedence. Alternatively, omit built-in backends from the backends array entirely and supply only your implementations.

What happens if my backend doesn't support getExec?

Throw an error from getExec as shown in the echo backend example. This is valid for stateless or ephemeral execution models where reconnection isn't meaningful. The Workspace runtime will propagate this error to callers attempting to reconnect.

How do I debug event stream issues in my custom backend?

Refer to packages/computer/src/runtime/wire.ts for the exact wire protocol. Ensure your stream emits events with monotonic seq numbers, includes all required fields (id, name, value), and always terminates with an exit event—otherwise consumers will hang awaiting completion.

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 →