Effect-native durable execution for Resonate.
Status: experimental. The public API and execution semantics are under active development.
- Resonate protocol
2026-04-01 - Effect
4.0.5(pinned because fiber scheduling affects positional child ordinals) - A runtime with standard
fetch, Web Streams,AbortController, Web Crypto,TextEncoder,TextDecoder, andTextDecoderStream
import { Effect } from "effect";
import { Resonate, type Workflow } from "@resonatehq/effect";
const resonate = new Resonate({ url: "http://localhost:8001" });
let factorial!: Workflow<number, number, unknown, never>;
factorial = resonate.register(
"factorial",
(context, n: number) =>
n <= 1 ? Effect.succeed(1) : Effect.map(context.run(factorial, n - 1), (child) => n * child),
{ version: 1 },
);
const program = factorial.run("factorial-6", 6);
console.log(await Effect.runPromise(program)); // 720run executes a durable child locally. rpc dispatches it through Resonate. Their beginRun and beginRpc
variants return an Effect-compatible Durable without waiting for its result.
At the root, beginRun is scoped so Effect services cannot outlive their Layer:
const program = Effect.scoped(
Effect.gen(function* () {
const left = yield* factorial.beginRun("left", 5);
const right = yield* factorial.beginRun("right", 6);
return yield* Effect.all([left, right]);
}),
);Activities can use arbitrary Effect services but cannot create or await durable operations. They require an explicit, idiomatic Effect retry schedule:
import { Effect, Schedule } from "effect";
const charge = resonate.registerActivity("charge", (_info, payment: Payment) => PaymentGateway.charge(payment), {
version: 1,
});
const checkout = resonate.register(
"checkout",
(context, payment: Payment) =>
context.act(charge, payment, {
retry: Schedule.exponential("100 millis").pipe(Schedule.compose(Schedule.recurs(5))),
}),
{ version: 1 },
);The activity boundary is checked at runtime, including captured Durable values.
Start a stock server with resonate dev, then run:
# README flagship: plan searches, RPC fan-out, synthesize a report
npm run example:research -- "What is durable execution?"
# Recursive location transparency: run for even n, rpc for odd n
npm run example:fibonacci -- 10The research example uses deterministic activity stubs so it runs without API credentials. Replace those activity bodies with model and search services; the durable workflow remains unchanged.
- Child IDs use Dewey notation:
root:1,root:1.1, and so on. - Replays always resolve the exact positive workflow version stored with the invocation.
- Workflow code must only block on durable operations. Put external I/O and ordinary asynchronous work in an activity.
- Starting durable operations concurrently is supported when their Effect launch order is deterministic. The SDK serializes create acknowledgements and records an operation fingerprint to detect replay mismatches.
- Interrupting a durable wait inside a workflow does not cancel its durable child. Durable
raceandtimeoutcombinators are intentionally unsupported until the server has child-abandonment semantics. - Effect finalizers run when an attempt suspends or fails. Use them only for attempt-local resource cleanup, never for business compensation.
npm run validateThe test suite includes recursive local and RPC factorial chains, Dewey ID checks, version pinning, suspension and resumption, activity retries, runtime activity-boundary checks, replay fingerprints, and lifecycle failure cases.