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.
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 state | What happens next |
|---|---|
| Completed activity | Return its recorded result |
| Unanswered Human question | Keep waiting for its recorded response |
| New operation | Perform the work and record its outcome |
| Managed operation with uncertain external outcome | Require 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 saveAfter 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