Bun
Run Tardigrade actors locally or on a Bun server.
The Bun platform runs an actor backed by SQLite. Each actor instance has a supervisor SQLite database, and each thread has its own SQLite database, runtime, and checkpoint. Cloudflare and Celld use the same database boundaries through separate Durable Objects.
Run locally
tdg init creates a server.ts entry that mounts actor.ts and serves its API. Start it through the generated package script:
bun run dev
The script loads .dev.vars, watches the TypeScript sources, and restarts the server after an edit. The server listens on port 4242 by default. Its health endpoint is available at /healthz.
Run the Cloudflare Worker locally when you need the Durable Object runtime:
bun run dev:cloudflare
Mount an actor
The generated server reads model configuration from wrangler.jsonc and builds application services through ./services:
import { Effect, Schema } from "effect"
import { actorContext } from "tardie/agent"
import { createBunHost, methodHttp } from "tardie/platform/bun"
import definition from "./actor"
import { services } from "./services"
const configPath = process.env.TARDIGRADE_CONFIG_PATH ?? "wrangler.jsonc"
const Config = Schema.Struct({
vars: Schema.Struct({ TARDIGRADE_CONFIG: Schema.Struct({ models: Schema.Unknown }) }),
})
const config = Schema.decodeUnknownSync(Config)(Bun.JSONC.parse(await Bun.file(configPath).text()))
const host = await Effect.runPromise(createBunHost({
actor: definition,
actorContext,
storage: process.env.TARDIGRADE_ACTOR_DATA ?? ".tardigrade/data",
services: () => services(config.vars.TARDIGRADE_CONFIG.models, process.env),
}))
const server = Bun.serve({
hostname: "127.0.0.1",
port: Number(process.env.PORT ?? 4242),
idleTimeout: 0,
fetch: methodHttp(host, {
...(process.env.TARDIGRADE_TOKEN ? { token: process.env.TARDIGRADE_TOKEN } : {}),
}),
})
services(coordinate, runtime) supplies Effect layers for each thread. Merge application layers with the generated services here or in services.ts. actorContext selects the services visible to agent setup; actors with custom requirements supply their own selector.
methodHttp(host) discovers the methods returned by actor setup and exposes thread creation, invocation, cancellation, and event reads. Set TARDIGRADE_TOKEN to require bearer authentication. On shutdown, call await server.stop(true) before await Effect.runPromise(host.close); the generated server handles process signals for you.
Optional backup
Provide a RemoteBackup layer through createBunHost({ backup, ... }), then call Effect.runPromise(host.backup) to capture and save a host checkpoint. Your application chooses when to call it.
For filesystem backups, bunBackup({ directory, namespace }) from tardie/platform/bun supplies the layer. Choose a backup directory outside host storage. checkpoint.maxBytes overrides the capture limit in the exported DEFAULT_CHECKPOINT_POLICY; capture fails if storage changes while it is being read.
Storage
Generated servers store actor instances under .tardigrade/data/. Each instance database filename is the base64url encoding of [actor, instance] as JSON, followed by .sqlite. Thread databases live in a sibling <instance-database>.threads/ directory, with base64url-encoded thread IDs as filenames.
The host persists events before running the work they enable, so it can resume unfinished work after the process restarts.
Configure
tdg setup writes model configuration to wrangler.jsonc and local credentials to .dev.vars. The generated development script loads .dev.vars. A hosted process can provide the same credential names through its environment.
Set PORT to change the HTTP port and TARDIGRADE_ACTOR_DATA to change the storage directory used by the generated server.
createBunHost({ promises: { timeoutMs: 60_000, pollIntervalMs: 1_000 }, ... }) configures deferred work at the host. bunPromises(runtime, ...) inherits this policy; its policy option can override resolver scheduling. DEFAULT_PROMISE_POLICY is exported from tardie/core. Each promise records an absolute deadline, so a restart preserves its expiry. Expiry rejects with PromiseTimedOut without invoking backend cancellation; scheduled clocks receive the timeout budget after their scheduled time.
Background library methods override the host waiting budget with .annotate(MethodPromiseTimeout, timeoutMs) from tardie/libraries. modelServices({ timeoutMs }) carries the configured model timeout into inference promises. Overrides become fixed recorded deadlines, so replay does not renew the waiting budget.