LaufwerkLaufwerk
API reference

Timers and signals

DurableClock and DurableDeferred options, results, completion tokens and examples.

These are Effect Workflow primitives imported from @effect/workflow. They require the workflow engine. For a person answering a question, use Human, which also registers a visible Studio interaction.

DurableClock.sleep(options)

PropertyTypeRequiredDefault / meaning
namestringYesStable timer identity
durationDuration.DurationInputYesDelay; e.g. "5 minutes" or Duration.minutes(5)
inMemoryThresholdDuration.DurationInputNo"60 seconds"; delays at or below this use an in-memory sleep inside an activity

Returns an Effect of void. Longer delays schedule an engine clock and await a durable deferred. Short in-memory delays can restart if interrupted before their activity result is recorded. A running engine/worker is needed to drive completion; this function is not an operating-system wakeup or cron scheduler.

Keep laufwerk studio or laufwerk serve running for automatic wakeups of bundled consumer runs. The server checks waiting runs for due clocks. A one-shot CLI run can return while the workflow is waiting; it does not leave a permanent timer worker behind. Runs registered directly through the library need their caller to drive them again. A pending unanswered human interaction also prevents automatic timer-driven resumption of that run.

DurationInput accepts an Effect Duration, a number of milliseconds, a bigint of nanoseconds, an [seconds, nanoseconds] tuple, or a supported unit string. Prefer explicit units. To force a positive delay through the durable-clock path, pass Duration.zero as the threshold. Zero duration completes immediately.

DurableClock.make({ name, duration }) returns the definition with name, decoded duration and deferred; it does not itself schedule the clock.

timer-example.ts
import { DurableClock } from "@effect/workflow";
import { Duration, Effect } from "effect";
import { Human } from "@laufwerk/sdk";

export const remind = () => Effect.gen(function* () {
  yield* DurableClock.sleep({
    name: "review-delay", duration: "5 minutes", inMemoryThreshold: Duration.zero,
  });
  yield* Human.notify({ key: "review-reminder", title: "Review is ready to continue" });
});

DurableDeferred.make(name, options?)

Creates a named completion signal. Construction is synchronous; awaiting it is the operation that waits.

Argument/propertyTypeRequiredDefault
namestring, first argumentYesStable signal identity
options.successEffect SchemaNoSchema.Void
options.errorEffect SchemaNoSchema.Never

Returns DurableDeferred<Success, Error> with name, successSchema, errorSchema, exitSchema, and withActivityAttempt. The latter is an Effect producing a deferred whose name incorporates the current activity attempt.

Waiting and completing

FunctionArgumentsResult
awaitDeferredEffect of its decoded success value or failure
intoEffect, deferred (also curried)Records the Effect's exit into the deferred and returns its value/failure
tokenDeferredEffect of a completion token for the current workflow execution
tokenFromExecutionIdDeferred, { workflow, executionId: string }Token for an explicit definition and execution
tokenFromPayloadDeferred, { workflow, payload }Effect of token derived from that workflow's input identity
succeedDeferred, { token, value }Effect of void; value matches success schema
failDeferred, { token, error }Effect of void; error matches failure schema
failCauseDeferred, { token, cause }Effect of void; cause is an Effect Cause
doneDeferred, { token, exit }Effect of void; exit is an Effect Exit

Completion functions require an engine, including when called outside the waiting workflow. A token is a branded string identifying workflow name, execution ID and deferred name; it is not authentication. TokenParsed.fromString(token) decodes those three string fields; TokenParsed.encode(parsed) / parsed.asToken encode them. Token also exposes an Effect branded-string schema.

signal-example.ts
import { DurableDeferred } from "@effect/workflow";
import { Effect, Schema } from "effect";

const result = DurableDeferred.make("checked-result", { success: Schema.String });

export const produceAndRead = () => Effect.gen(function* () {
  yield* DurableDeferred.into(Effect.succeed("checked"), result);
  return yield* DurableDeferred.await(result);
});

An external signal integration must deliver completion through a configured engine. defineHttpRoutes currently exposes startWorkflow, not a general completeDeferred method. A bare deferred does not automatically appear as a Human request in Studio.

Durable races

DurableDeferred.raceAll({ name, success, error, effects }) takes a stable string name, schemas for the winner's result/failure, and a nonempty array of Effects. It records the race outcome. Activity.raceAll(name, activities) takes a stable name and a nonempty array of activities and infers their result schemas. Ordinary Effect.raceAll alone does not record which branch won across workflow replay.

Advanced upstream queues and rate limits

@effect/workflow also exports DurableQueue and DurableRateLimiter. They need additional persisted-queue/rate-limiter services and workers. Laufwerk's standard consumer runtime does not automatically supply those services. They are not the same as Laufwerk's run admission queue or Docker resource limits.

DurableQueue.make(options)

PropertyTypeRequired / default
namestringRequired
payloadEffect Schema or struct fieldsRequired
idempotencyKey(decodedPayload) => stringRequired
successEffect SchemaOptional; Schema.Void
errorEffect SchemaOptional; Schema.Never

The definition exposes name, payloadSchema, idempotencyKey and deferred. DurableQueue.process(queue, payload, { retrySchedule? }) queues work and awaits its result; the optional schedule handles persisted-queue errors. DurableQueue.makeWorker(queue, handler, { concurrency?: number }) returns a long-running Effect; DurableQueue.worker supplies the worker as a layer. The handler maps decoded payload to an Effect of the declared result/error. Concurrency defaults to 1. Both producer and worker require a PersistedQueueFactory and the workflow engine.

DurableRateLimiter.rateLimit(options)

PropertyTypeRequired / meaning
namestringRequired; durable activity identity
keystringRequired; rate-limit bucket identity
windowDuration.DurationInputRequired; rate window
limitnumberRequired; allowed token budget
algorithm"fixed-window" | "token-bucket"Optional; underlying limiter default
tokensnumberOptional; underlying limiter's token-count default

Returns a yieldable Activity of void with RateLimitStoreError; it consumes capacity and delays via DurableClock when needed. It requires the upstream RateLimiter service. Simply importing it into a consumer does not configure distributed rate limiting.

On this page