Cloudflare

Deploy Tardigrade actors to Cloudflare Workers and Durable Objects.

Each actor instance has an ActorDO with its supervisor database. Each thread has a separate ThreadDO with its own SQLite database, runtime, checkpoint, and recovery alarm. Bun uses the same database boundaries with SQLite files. The Worker routes an instance ID to its directory, which forwards thread requests to their owning objects:

ts
import { actorContext } from "tardie/agent"
import { createActorWorker } from "tardie/platform/cloudflare"
import definition from "./actor"
import { services } from "./services"

const worker = createActorWorker({
  actor: definition,
  actorContext,
  services: () => services(),
})
export const ActorDO = worker.ActorObject
export const ThreadDO = worker.ThreadObject
export default worker

services comes from the generated services.ts and supplies model and tool implementations. actorContext selects the services visible to agent setup. createActorWorker supplies the Worker router, ActorObject directory class, and ThreadObject runtime class. Export both classes and bind both namespaces. createActorObject and createThreadObject supply the classes separately for custom Worker routing. Thread object names encode the full actor, instance, and thread coordinate through cloudflareThreadName.

Verify locally

Start with a project from the Quickstart, then install Wrangler in that project. Use a release that supports the compatibility_date in wrangler.jsonc; updating Wrangler also updates its local runtime:

bash
bun add --dev wrangler@latest

Add each configured provider credential to .dev.vars. Interactive tdg setup saves credentials there; credentials supplied only through the environment must also be available to Wrangler. Authentication is optional: set TARDIGRADE_TOKEN to enable it.

Start the Worker:

bash
bun run dev:cloudflare

When authentication is enabled, export TARDIGRADE_TOKEN in the calling shell with the value saved in .dev.vars; the CLI reads it automatically. Wrangler reads that file for the Worker; it does not export its values to another shell. Use the URL printed by Wrangler when allocating and calling a thread. /healthz is public; when authentication is enabled, the remaining commands require the token from .dev.vars:

bash
curl http://localhost:8787/healthz
tdg thread create --name quickstart --url http://localhost:8787
tdg call message '{"text":"What is the weather in Singapore?"}' --thread quickstart --url http://localhost:8787

Configure

tdg init creates wrangler.jsonc and the Worker entry for your actor. Run tdg setup to select a model and write its visible configuration to the project.

The generated manifest binds ACTORS to ActorDO and THREADS to ThreadDO, and declares both classes in its SQLite migration. Model metadata comes from models.lock.json, bundled with the Worker.

Store the Tardigrade token and each model provider credential as Wrangler secrets:

bash
bunx wrangler secret put TARDIGRADE_TOKEN
bunx wrangler secret put OPENAI_API_KEY

Use the credential name configured for your provider in place of OPENAI_API_KEY.

Deploy

bunx wrangler deploy

The deployed Worker exposes the same thread creation, method invocation, cancellation, and event routes used during local development.

Independent routing requires directory entries with independent placement. Colocated journals require an explicit data migration before these factories can route them. For an existing Worker namespace, register ThreadDO in a new SQLite migration tag and add its THREADS binding.

Recovery

The host commits a watchdog record and an immediate alarm with journal admission, then awaits storage.sync() before acknowledging the commit or starting execution. The directory and each thread handle their own alarm(). A thread alarm restores that thread from its own database; it does not drive sibling runtimes. Each recovery attempt commits its counters and replacement alarm, then synchronizes storage before driving the runtime.

Effect and promise settlements advance the progress cursor and reset consecutive failures. Settled runtimes clear their wake; remote promises park until their recorded deadline. Terminal failures and exhausted budgets retain a blocked record and clear the wake. New messages preserve the block.

Configure recovery at Worker construction:

ts
createActorWorker({
  // actor, services, actorContext
  watchdog: {
    policy: {
      attemptTimeoutMs: 30_000,
      keepAliveIntervalMs: 30_000,
      retryIntervalMs: 1_000,
      maxRetryIntervalMs: 30_000,
      maxAttempts: 20,
      maxNoProgressAttempts: 5,
    },
  },
  promises: { timeoutMs: 60_000, pollIntervalMs: 1_000 },
})

DEFAULT_WATCHDOG_POLICY and DEFAULT_PROMISE_POLICY are exported from tardie/core. The watchdog retries typed failures by default; defects and WatchdogTerminalError block recovery. Supply watchdog.retryable(error) to classify application failures. Promise expiry records PromiseTimedOut without requesting backend cancellation. A scheduled clock receives its timeout budget after its scheduled time. Configure a dedicated promise resolver's observation cadence with createCloudflarePromiseResolver({ policy: { pollIntervalMs }, ... }); the host supplies each recorded absolute deadline.

Background library methods can override the host's promise timeout. The override is recorded as an absolute deadline when the handle is accepted; replay retains that deadline.

ts
import { Schema } from "effect"
import { Rpc } from "effect/unstable/rpc"
import { MethodExecution, MethodPromiseTimeout } from "tardie/libraries"

const research = Rpc.make("research", {
  payload: Schema.Struct({ question: Schema.String }),
  success: Schema.String,
  error: Schema.String,
})
  .annotate(MethodExecution, "background")
  .annotate(MethodPromiseTimeout, 300_000)

The host keeps live local fibres active with alarm heartbeats at keepAliveIntervalMs. Alarm handlers return while recovery runs. Heartbeats preserve the promise deadline and recovery counters; completion releases the wake, and a process crash returns to budgeted recovery. Remote promises park without keeping the local fibre alive. modelServices({ timeoutMs }) uses the configured model timeout for inference promises too.

Local promise recovery reruns the originating act with its recorded reference and deadline. The act must recreate its producer and tolerate repeated execution; external operations must deduplicate by the reference. Cloudflare's finite alarm redeliveries leave a coverage gap if repeated crashes occur before the replacement alarm becomes durable.