LaufwerkLaufwerk
Learn Laufwerk

Durability, waiting and recovery

Understand what replay saves, what it repeats, and when an operator must resolve uncertainty.

Durability means recorded results can be reused after a restart. It does not mean arbitrary code runs exactly once or that the runtime saves a JavaScript stack.

Put changing work at a recorded boundary

Save this at laufwerk/workflows/stamped-greeting/workflow.ts.

workflow.ts
import { Activity, Workflow } from "@effect/workflow";
import { Effect, Schema } from "effect";

export const workflow = Workflow.make({
  name: "stamped-greeting",
  payload: { requestId: Schema.String, name: Schema.String },
  success: Schema.Struct({ greeting: Schema.String, createdAt: Schema.String }),
  idempotencyKey: input => input.requestId,
});
export const layer = workflow.toLayer(input => Effect.gen(function* () {
  const createdAt = yield* Activity.make({
    name: "created-at", success: Schema.String,
    execute: Effect.sync(() => new Date().toISOString()),
  });
  return { greeting: `Hello ${input.name}`, createdAt };
}));

Once the activity result is recorded, replay reuses that timestamp. A timestamp calculated directly in replayed code could change. The same applies to random choices and reads from changing external systems: put them in activities when the result must remain stable for this run.

Follow the replay decision

Stored stateWhat happens next
Completed activityReturn its recorded result
Unanswered Human questionKeep waiting for its recorded response
New operationPerform the work and record its outcome
Managed operation with uncertain external outcomeRequire reconciliation before proceeding

The runtime also tracks managed session/workspace operations. Those records add safeguards around external work. A plain activity around an HTTP request does not magically make that request exactly once.

The failure window that matters

Send external request → external system performs it → save local receipt

                                  process can stop before this save

After that interruption, a missing local receipt cannot tell you whether the external action happened. Use a stable external idempotency key where supported. Otherwise inspect the external system before recording a recovered result. A new workflow identity can duplicate the original action.

Waiting needs a continuation

Studio or serve can drive pending work while running. Durable timers need an active worker to observe that they are due; closing the one-shot CLI does not leave a background timer process. A human wait retains its question even without an open browser.

For an interrupted run, inspect status RUN_ID --json, stop old workers and use the recovery procedure. Never paste a sample success result without verifying the actual external outcome.

Reference: Activities · Timers and signals


← Human input and review · Next: Execution and isolation →

On this page