Skip to content

Cloudflare Filestore Workspace

The CloudflareFileStoreWorkspace is the lightest Cloudflare option for durable file storage. Files live in the agent's own Durable Object SQLite via @cloudflare/shell's Workspace. No container, no cold start, optional R2 binding for large-file spill.

When to use

  • Cloudflare DO–hosted agents that need durable file storage.
  • You want files to survive DO hibernation cleanly with no recovery dance.
  • You don't need a shell or code interpreter — just files.

If you need shell or code execution on Cloudflare, use Cloudflare Sandbox.

Capabilities supported

CapabilitySupported
fs
shell
code
snapshot

Provider config

typescript
interface CloudflareFileStoreWorkspaceConfig {
  kind: 'cloudflare-filestore';
  /** Scope for table names within the DO's SQLite. Defaults to a sanitized form of the session ID. */
  namespace?: string;
  /** Name of the R2 bucket binding on `env`, used for large-file spill. */
  r2Binding?: string;
  /** Byte threshold above which files spill to R2. Defaults to @cloudflare/shell's 1.5MB. */
  inlineThreshold?: number;
}

Namespace handling

@cloudflare/shell requires the namespace to match /^[a-zA-Z][a-zA-Z0-9_]*$/. Most session IDs don't satisfy that (dashes, etc.), so the provider hex-encodes invalid session IDs with an s_ prefix automatically. The transformation is deterministic, so the same session ID always yields the same namespace across reads.

If you set namespace explicitly in config, it must satisfy the regex — the provider throws on invalid explicit namespaces (no silent rewriting).

Wrangler setup

The agent DO and the filestore share the SAME DO. No separate sandbox container required.

toml
[[durable_objects.bindings]]
name = "AGENTS"
class_name = "MyAgentServer"

[[migrations]]
tag = "v1"
new_sqlite_classes = ["MyAgentServer"]

# Optional: R2 bucket for large-file spill
[[r2_buckets]]
binding = "FILES_R2"
bucket_name = "my-files"

new_sqlite_classes is required — @cloudflare/shell uses SQLite-backed DO storage.

Provider wiring

typescript
import { createAgentServer, AgentRegistry, CloudflareFileStoreWorkspaceProvider } from '@helix-agents/runtime-cloudflare';
import type { WorkspaceProvider } from '@helix-agents/core';

interface Env {
  AGENTS: DurableObjectNamespace;
  FILES_R2?: R2Bucket;  // optional
  OPENAI_API_KEY: string;
}

export const MyAgentServer = createAgentServer<Env>({
  llmAdapter: (env) => /* ... */,
  agents: registry,
  workspaceProviders: (env, ctx) =>
    new Map<string, WorkspaceProvider>([
      ['cloudflare-filestore', new CloudflareFileStoreWorkspaceProvider(ctx, env as unknown as Record<string, unknown>)],
    ]),
});

The provider takes (ctx, env) because it needs ctx.storage.sql for the SQLite-backed file store. The double-cast as unknown as Record<string, unknown> is required because the typed Env doesn't have an index signature.

Cross-hibernation behavior

Cloudflare Durable Objects can hibernate after periods of inactivity. When the DO wakes:

  1. The framework calls provider.resolve(ref) with the persisted WorkspaceRef.
  2. The ref payload contains the namespace + optional R2 binding name.
  3. resolve() reattaches by constructing a new Workspace with the same namespace against ctx.storage.sql — same data shows up because the SQLite tables persist.

No data loss. No recovery dance.

Auto-injected tools

All fs tools (see FileSystem module for schemas):

  • workspace__<name>__read_file, write_file, edit_file, ls, glob, grep, stat, mkdir, rm

Code sample (full integration)

typescript
// agent.ts
import { defineAgent } from '@helix-agents/core';

export const NoteTakingAgent = defineAgent({
  name: 'note-taker',
  llmConfig: { model: yourModel },
  workspaces: {
    notes: {
      provider: { kind: 'cloudflare-filestore' },
      capabilities: { fs: true },
    },
  },
  systemPrompt: `You're a note-taking agent. Use workspace__notes__write_file to save notes to /notes/<slug>.md`,
});

// my-agent-server.ts
const registry = new AgentRegistry();
registry.register(NoteTakingAgent);

export const MyAgentServer = createAgentServer<Env>({
  llmAdapter: (env) => new VercelAIAdapter(),
  agents: registry,
  workspaceProviders: (env, ctx) =>
    new Map([['cloudflare-filestore', new CloudflareFileStoreWorkspaceProvider(ctx, env as unknown as Record<string, unknown>)]]),
});

// worker.ts
export { MyAgentServer };
export default {
  async fetch(req, env): Promise<Response> {
    const sessionId = new URL(req.url).searchParams.get('session') ?? `demo-${Date.now()}`;
    const stub = env.AGENTS.get(env.AGENTS.idFromName(sessionId));
    return stub.fetch(req);
  },
};

Limitations

  • fs only. No shell, code, or snapshot. Use Cloudflare Sandbox for those.
  • DO-local. Files are scoped to the agent's DO instance. They are not shared across DO instances; cross-DO workspace sharing is reserved for a future plan.
  • Workflows runtime not supported. Workspaces require the DO runtime path (createAgentServer) — Cloudflare Workflows currently has no native workspace providers.

Source

Released under the MIT License.