FabricFabricHarness
Deployment

Cloudflare Workers + Sandbox

Deploy to Cloudflare Workers with Durable Object sessions and Cloudflare Sandbox.

The Cloudflare target emits an unbundled Worker entrypoint, a Durable Object session store, and a Cloudflare Sandbox container binding for finite jobs and persistent agents. Wrangler owns the Cloudflare bundle; Fabric Harness emits the entry, bindings, and config. Each persistent agent/instance pair is routed to one Durable Object, which serializes turns and persists named sessions in Durable Object SQLite.

Finite runs remain authoritative in one Durable Object per run. A separate, singleton FabricHarnessRunRegistryObject stores only tenant-scoped run pointers so client.runs.list() can discover runs across those objects. Registry mirror writes are best-effort and never turn a successful run admission or settlement into a failure. A terminal pointer is an upsert, so it heals a start pointer lost during a transient registry fault.

Before production, run the live smoke workflow in your Cloudflare account to validate bindings, Durable Object persistence, R2 access, and Sandbox container startup with your account limits.

Quickstart

npx @fabric-harness/cli init --template cloudflare --dir my-edge-agent
cd my-edge-agent
npm install
npx fabric-harness dev --target cloudflare --port 8787

The template uses the Cloudflare Workers AI binding (env.AI) so the Worker can run inference without external model API keys.

Build

fh build --target cloudflare
cd .fabricharness/build/cloudflare
npm install

Output:

.fabricharness/build/cloudflare/
  dist/worker.ts
  wrangler.jsonc
  Dockerfile         # default Cloudflare Sandbox image
  manifest.json
  README.cloudflare.md

Develop locally

fh dev --target cloudflare

This wraps wrangler dev so the same bundler runs in dev and deploy. Two reload paths run simultaneously:

  • Wrangler watches the bundle's transitive import graph and reloads workerd on body edits to your agent files.
  • Fabric Harness's structural watcher watches .fabricharness/jobs/ and .fabricharness/agents/, then regenerates the entry whenever the definition set changes.

Net result: you can edit job bodies, add jobs, or change triggers: { webhook: true } without restarting the dev server.

Or if you've already built once and just want raw wrangler:

npx wrangler dev

Deploy

fh build --target cloudflare
cd .fabricharness/build/cloudflare
npx wrangler deploy

Routes

The Cloudflare Worker exposes both execution models:

  • GET /health · GET /ready · GET /manifest
  • POST /jobs/:name — invoke a finite job and receive { result, runId }
  • POST /jobs/:name?wait=false — admit an asynchronous run; an Idempotency-Key reuses its run
  • GET /runs?status=&job=&limit=&cursor= — list tenant-visible finite runs in reverse chronological order
  • GET /runs/:runId · GET /runs/:runId/events?offset=0 — inspect finite runs and events
  • POST /runs/:runId/abort — abort an active finite run
  • POST /agents/:name/:instanceId?wait=false with { message, session? } — durably admit a persistent submission
  • GET /agents/:name/:instanceId/submissions/:submissionId?session=default — inspect settlement
  • GET /agents/:name/:instanceId/conversation?session=default&offset=0 — read the offset stream
  • POST /agents/:name/:instanceId/abort?session=default — abort queued or active submissions
  • GET /agents/:name/:instanceId?session=default — inspect a named persistent session
  • DELETE /agents/:name/:instanceId?session=default — delete that named session
  • GET /sessions/:runId — inspect the stored run
  • GET /sessions/:runId/timeline · /metrics · /tasks · /approvals · /artifacts

Webhook trigger gating applies on the Worker too: in production mode (FABRIC_ENV=production), only definitions with triggers.webhook === true are exposed publicly.

Cron Triggers

Declare schedules on finite jobs:

export default defineAgent({
  name: 'daily-report',
  triggers: { schedule: '0 16 * * 1-5' }, // Cloudflare cron is UTC
  async run({ input }) {
    return buildReport(input);
  },
});

The Cloudflare build adds each unique expression to wrangler.jsonc and emits scheduled(). Scheduled runs use deterministic IDs, persist run state and offset events in a per-run Durable Object, and deduplicate repeated delivery of the same occurrence. HTTP and cron execution share the same job function and { runId, acceptedAt, statusUrl, eventsUrl } receipt contract. Their pointers are indexed in FABRIC_HARNESS_RUN_REGISTRY, the same registry used by HTTP runs.

Choosing a Cloudflare sandbox mode

Fabric Harness supports two Cloudflare sandbox modes.

Containers / Cloudflare Sandbox

Use this mode when your agent needs Linux shell commands, package managers, language toolchains, bash, grep, read, write, and edit tools.

export default {
  sandbox: {
    backend: 'cloudflare',
    mode: 'sandbox',
    binding: 'Sandbox',
    cwd: '/workspace',
  },
};

Computer / @cloudflare/computer

Use this early-preview mode when you want a lightweight, SQLite-backed durable Workspace and a Worker Loader-backed just-bash runtime. It provides the standard bash, grep, glob, read, write, and edit tools, but it is not a Linux container: native binaries, package managers, and language toolchains still require Cloudflare Sandbox.

export default {
  sandbox: {
    backend: 'cloudflare',
    mode: 'computer',
    loaderBinding: 'LOADER',
    cwd: '/workspace',
  },
};

The generated wrangler.jsonc adds:

{
  "compatibility_flags": ["nodejs_compat", "experimental"],
  "worker_loaders": [{ "binding": "LOADER" }]
}

Typed Workspace access is exported from @fabric-harness/cloudflare/computer. The generated Worker also exports WorkspaceServiceProxy and hosts one workspace in each Fabric session Durable Object. Existing @cloudflare/shell data is not migrated. The old shell-workspace configuration value is a deprecated alias for one release window.

If Cloudflare sandboxing isn't configured, the Worker falls back to Fabric's empty sandbox.

The build always gives an explicitly configured Cloudflare sandbox precedence over the lightweight virtual default added by the default-import defineAgent(). This means the same agent source can stay infrastructure-free locally and use the container or Computer workspace selected by the deployment target without changing its prompt and session code.

Verify the Sandbox contract

The runnable examples/with-cloudflare-sandbox project includes a sandbox-certification job. It exercises the same nine behaviors used by the other maintained remote sandbox adapters:

  • shell execution and binary file round trips;
  • working-directory and environment propagation;
  • stdout/stderr collection;
  • timeout and abort, including termination of the native Cloudflare process;
  • portable reference encoding, reconnect, and provider cleanup.

Build and run the Worker locally with the actual Cloudflare Sandbox container:

cd examples/with-cloudflare-sandbox
pnpm fh build --target cloudflare
cd .fabricharness/build/cloudflare
pnpm exec wrangler dev --local

In another terminal, request the report:

curl --fail --request POST http://localhost:8787/jobs/sandbox-certification \
  --header 'content-type: application/json' \
  --data '{"input":{"credentialed":false}}'

The response contains a schema-v1, secret-free report with one result per check. A passing report has ok: true, provider: "cloudflare-sandbox", and nine status: "passed" rows. Protected live CI uses credentialed: true and retains the same JSON as certification evidence. The generated container image and @cloudflare/sandbox dependency are kept on the supported 0.9 line so local and hosted results exercise the same SDK contract.

How it maps to Fabric primitives

Fabric primitiveCloudflare equivalent
Session storeDurable Object SQLite (one DO per persistent agent instance; finite runs route by run id)
Run discoveryTenant-filtered registry DO containing pointers; per-run DOs remain authoritative
SandboxEnvCloudflare Sandbox container binding via @cloudflare/sandbox, or a durable Computer workspace via @cloudflare/computer
Model providerenv.AI Workers AI binding via CloudflareWorkersAIModelProvider (optional; HTTP providers also work)
Webhook triggerPOST /jobs/:name
Schedule triggerWorker scheduled() + triggers.crons
Persistent promptPOST /agents/:name/:instanceId
Health/manifestGET /health, GET /manifest

Workers AI binding (no API tokens)

@fabric-harness/cloudflare/workers-ai ships a ModelProvider that routes inference through env.AI.run() instead of HTTP. Zero API tokens, zero egress, runs at the edge. Workers AI accepts the OpenAI Chat Completions request body, so the provider serializes through the SDK's standard OpenAI helpers.

import { CloudflareWorkersAIModelProvider } from '@fabric-harness/cloudflare/workers-ai';

export default {
  async fetch(request: Request, env: Env) {
    const fabric = await init({
      modelProvider: new CloudflareWorkersAIModelProvider({
        binding: env.AI,
        defaultModel: '@cf/meta/llama-3.1-8b-instruct',
        // Optional: route through Cloudflare AI Gateway
        // gateway: { id: 'my-gateway', skipCache: false, cacheTtl: 3600 },
      }),
    });
    // ...
  },
};

Add the binding to wrangler.jsonc:

{
  "ai": { "binding": "AI" }
}

You can still use HTTP providers (Anthropic, OpenAI-compatible, Vercel AI Gateway) on the Cloudflare target — store the API key as a Workers Secret and reference it via env.

Account validation

Run the Cloudflare smoke in your account before rollout:

FABRIC_CLOUDFLARE_TEST=1 \
FABRIC_CLOUDFLARE_WORKER_URL=https://<worker>.<subdomain>.workers.dev \
FABRIC_CLOUDFLARE_ARTIFACT_DIR=/path/to/.fabricharness/build/cloudflare \
CLOUDFLARE_ACCOUNT_ID=<account-id> \
pnpm --filter @fabric-harness/cloudflare test -- live.test.ts

The repository test suite also runs local workerd tests without account credentials. It admits concurrent persistent requests, verifies FIFO and attachment materialization, aborts finite and persistent work, deletes a session, kills workerd during a tool call, restarts with the same Durable Object storage, and verifies conservative interrupted-tool settlement. The account smoke adds the provider-specific bindings, Sandbox container, and R2 checks.

Limits and considerations

  • Worker request/CPU limits apply; long-running workflows belong on the Temporal worker target.
  • Persistent turns use a Durable Object submission queue with leases, FIFO execution, restart reconciliation, attachment materialization, abort, deletion, and offset conversation streams.
  • The run registry is a discovery index, not a second source of truth. Monitor mirror errors; terminal writes self-heal missing starts, but an account-level outage can temporarily omit an active pointer while direct run-id inspection continues to work.
  • Use Temporal when workflows exceed Worker or Durable Object execution/storage limits, require long timers across many external activities, or need Temporal's workflow-history tooling.
  • Sandbox container start latency depends on the Cloudflare image; warm pools help.
  • Keep FABRIC_HARNESS_API_TOKEN, body limits, rate limits, and FABRIC_ENV=production trigger gating enabled for public deployments.
  • With FABRIC_ENV=production, an unset API token fails closed: only /health and /ready remain reachable without authentication.