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)
| Property | Type | Required | Default / meaning |
|---|---|---|---|
name | string | Yes | Stable timer identity |
duration | Duration.DurationInput | Yes | Delay; e.g. "5 minutes" or Duration.minutes(5) |
inMemoryThreshold | Duration.DurationInput | No | "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.
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/property | Type | Required | Default |
|---|---|---|---|
name | string, first argument | Yes | Stable signal identity |
options.success | Effect Schema | No | Schema.Void |
options.error | Effect Schema | No | Schema.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
| Function | Arguments | Result |
|---|---|---|
await | Deferred | Effect of its decoded success value or failure |
into | Effect, deferred (also curried) | Records the Effect's exit into the deferred and returns its value/failure |
token | Deferred | Effect of a completion token for the current workflow execution |
tokenFromExecutionId | Deferred, { workflow, executionId: string } | Token for an explicit definition and execution |
tokenFromPayload | Deferred, { workflow, payload } | Effect of token derived from that workflow's input identity |
succeed | Deferred, { token, value } | Effect of void; value matches success schema |
fail | Deferred, { token, error } | Effect of void; error matches failure schema |
failCause | Deferred, { token, cause } | Effect of void; cause is an Effect Cause |
done | Deferred, { 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.
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)
| Property | Type | Required / default |
|---|---|---|
name | string | Required |
payload | Effect Schema or struct fields | Required |
idempotencyKey | (decodedPayload) => string | Required |
success | Effect Schema | Optional; Schema.Void |
error | Effect Schema | Optional; 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)
| Property | Type | Required / meaning |
|---|---|---|
name | string | Required; durable activity identity |
key | string | Required; rate-limit bucket identity |
window | Duration.DurationInput | Required; rate window |
limit | number | Required; allowed token budget |
algorithm | "fixed-window" | "token-bucket" | Optional; underlying limiter default |
tokens | number | Optional; 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.