How to Use WorkspaceObserver and WorkspaceSpan for Monitoring Workspace Operations in Cloudflare Computer

The Cloudflare Computer SDK instruments every workspace operation with hierarchical spans that you can monitor by supplying a custom WorkspaceObserver when creating a Workspace, or by using the built‑in createCloudflareObserver adapter to forward traces to the Cloudflare runtime.

The Computer SDK provides lightweight, built‑in observability through the WorkspaceObserver and WorkspaceSpan interfaces defined in packages/computer/src/observe.ts. These abstractions wrap every significant workspace operation—syncs, command execution, and filesystem calls—in timed, attributed spans that you can route to any telemetry backend.

What Are WorkspaceObserver and WorkspaceSpan?

The WorkspaceObserver interface declares a single method that the Computer SDK calls around each instrumented operation:

interface WorkspaceObserver {
  span<T>(
    name: string,
    attrs: Record<string, boolean | number | string | undefined>,
    run: (span: WorkspaceSpan) => Promise<T>
  ): Promise<T>;
}

The WorkspaceSpan object passed to your run callback lets you attach dynamic attributes as operations progress:

interface WorkspaceSpan {
  setAttribute(key: string, value: boolean | number | string | undefined): void;
}

The generic helper withSpan in packages/computer/src/observe.ts implements the common wrapping logic: it records error information and supports an optional finalize callback for attaching late‑bound attributes such as byte counts or exit codes.

Operations That Emit Spans

According to the source in packages/computer/src/workspace.ts and packages/computer/src/shell.ts, the following operations are automatically instrumented:

  • workspace.connect – Emitted during #handleFor and #moduleHandleFor when establishing DOFS connections.
  • workspace.sync.push – Wrapped around the push() method in workspace.ts.
  • workspace.sync.pull – Wrapped around the #pullResolved private method.
  • workspace.runtime.exec – Emitted via CommandExecutor in shell.ts when running shell commands.
  • workspace.fs.<operation> – Routed through WorkspaceStub for filesystem calls.

If no observer is provided, the SDK falls back to noopObserver, a no‑op implementation that adds zero overhead.

Creating a Custom WorkspaceObserver

To capture spans for logging, metrics, or custom tracing, implement the WorkspaceObserver interface:

import { WorkspaceObserver, WorkspaceSpan } from "@cloudflare/computer/observe";

class ConsoleObserver implements WorkspaceObserver {
  async span<T>(
    name: string,
    attrs: Record<string, boolean | number | string | undefined>,
    run: (span: WorkspaceSpan) => Promise<T>,
  ): Promise<T> {
    console.log(`▶️ span start: ${name}`, attrs);
    const start = Date.now();

    const result = await run({
      setAttribute: (key, value) => {
        if (value !== undefined) console.log(`   ↳ ${key} =`, value);
      },
    });

    console.log(
      `✅ span end: ${name} – duration ${Date.now() - start} ms`,
    );
    return result;
  }
}

Wire the observer into your Workspace at instantiation:

import { Workspace } from "@cloudflare/computer";
import { SQLiteTestStorage } from "@cloudflare/dofs/testing";

const ws = new Workspace({
  storage: new SQLiteTestStorage(),
  observer: new ConsoleObserver(),
});

// Each operation emits a span
await ws.push();  // workspace.sync.push
await ws.pull();  // workspace.sync.pull
await ws.runtime.exec({ source: "echo hello", cwd: "/" });  // workspace.runtime.exec

Using the Cloudflare Runtime Adapter

For deployments on Cloudflare Workers or Durable Objects, use createCloudflareObserver from packages/computer/src/observe/cloudflare.ts to forward spans directly to the platform's native tracing system:

import { createCloudflareObserver } from "@cloudflare/computer/observe/cloudflare";

export default {
  async fetch(request, env, ctx) {
    const observer = createCloudflareObserver({ tracing: ctx.tracing });
    const ws = new Workspace({
      storage: ctx.storage,
      observer,
    });

    // Spans now appear in the Cloudflare dashboard tracing UI
    await ws.push();
    await ws.pull();
    return new Response("OK");
  },
};

The adapter maps WorkspaceObserver calls onto ctx.tracing.enterSpan, preserving span hierarchy and timing automatically.

Key Source Files

File Purpose
packages/computer/src/observe.ts Defines WorkspaceObserver, WorkspaceSpan, noopObserver, and withSpan.
packages/computer/src/observe/cloudflare.ts Implements createCloudflareObserver for Cloudflare runtime integration.
packages/computer/src/workspace.ts Core workspace; stores #observer and instruments push(), pull(), and connection handling.
packages/computer/src/shell.ts Wraps command execution; propagates observer to CommandExecutor.
packages/computer/src/observe.test.ts Validates observer behavior with noopObserver.
packages/computer/src/observe/cloudflare.test.ts Tests Cloudflare adapter span creation.

Summary

  • Supply a WorkspaceObserver when constructing Workspace to capture operation spans.
  • Implement span() to receive operation names, initial attributes, and a callback for executing the wrapped work.
  • Use setAttribute on the provided WorkspaceSpan to attach dynamic metadata during execution.
  • Use createCloudflareObserver for zero‑configuration integration with Cloudflare's native tracing.
  • Reference noopObserver in observe.ts if you need a minimal reference implementation.

Frequently Asked Questions

What is the default behavior if I don't provide a WorkspaceObserver?

The SDK uses noopObserver, a no‑op implementation that immediately invokes the run callback without recording timing or attributes. This ensures zero overhead when observability is not needed.

Can I attach attributes after a span has started?

Yes. The WorkspaceSpan.setAttribute method can be called at any point inside your run callback. This is useful for values that are only known partway through an operation, such as HTTP response statuses or byte counts written.

How do I integrate with OpenTelemetry instead of Cloudflare's native tracing?

Implement WorkspaceObserver to create Otel spans in your span() method, map setAttribute calls to span.setAttribute(), and handle timing and finalization using Otel's span.end(). The interface is deliberately minimal to make such adapters straightforward.

Is there a performance cost to using a custom observer?

The overhead depends on your implementation. The noopObserver path is designed to be free of allocations. For production use, keep your span() implementation lightweight—avoid blocking I/O inside the wrapper and delegate heavy work to background queues.

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 →