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 8787The 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 installOutput:
.fabricharness/build/cloudflare/
dist/worker.ts
wrangler.jsonc
Dockerfile # default Cloudflare Sandbox image
manifest.json
README.cloudflare.mdDevelop locally
fh dev --target cloudflareThis 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
workerdon 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 devDeploy
fh build --target cloudflare
cd .fabricharness/build/cloudflare
npx wrangler deployRoutes
The Cloudflare Worker exposes both execution models:
GET /health·GET /ready·GET /manifestPOST /jobs/:name— invoke a finite job and receive{ result, runId }POST /jobs/:name?wait=false— admit an asynchronous run; anIdempotency-Keyreuses its runGET /runs?status=&job=&limit=&cursor=— list tenant-visible finite runs in reverse chronological orderGET /runs/:runId·GET /runs/:runId/events?offset=0— inspect finite runs and eventsPOST /runs/:runId/abort— abort an active finite runPOST /agents/:name/:instanceId?wait=falsewith{ message, session? }— durably admit a persistent submissionGET /agents/:name/:instanceId/submissions/:submissionId?session=default— inspect settlementGET /agents/:name/:instanceId/conversation?session=default&offset=0— read the offset streamPOST /agents/:name/:instanceId/abort?session=default— abort queued or active submissionsGET /agents/:name/:instanceId?session=default— inspect a named persistent sessionDELETE /agents/:name/:instanceId?session=default— delete that named sessionGET /sessions/:runId— inspect the stored runGET /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 --localIn 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 primitive | Cloudflare equivalent |
|---|---|
| Session store | Durable Object SQLite (one DO per persistent agent instance; finite runs route by run id) |
| Run discovery | Tenant-filtered registry DO containing pointers; per-run DOs remain authoritative |
SandboxEnv | Cloudflare Sandbox container binding via @cloudflare/sandbox, or a durable Computer workspace via @cloudflare/computer |
| Model provider | env.AI Workers AI binding via CloudflareWorkersAIModelProvider (optional; HTTP providers also work) |
| Webhook trigger | POST /jobs/:name |
| Schedule trigger | Worker scheduled() + triggers.crons |
| Persistent prompt | POST /agents/:name/:instanceId |
| Health/manifest | GET /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.tsThe 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, andFABRIC_ENV=productiontrigger gating enabled for public deployments. - With
FABRIC_ENV=production, an unset API token fails closed: only/healthand/readyremain reachable without authentication.