From 565d49bcb611ea18484cf4f8beaff1b719be51f4 Mon Sep 17 00:00:00 2001
From: Dylan Slack
Date: Fri, 23 Oct 2026 10:13:44 +0200
Subject: [PATCH 01/23] feat(runtime)!: report worker callback outcome instead
of run state
BREAKING CHANGE: POST /runs/:runId/effects/:effectId/:attempt/:action returns { status, cancelRequested } instead of the run state. createLooms().workerCallback and the HTTP client return the same shape. Heartbeats may carry details that the next attempt's task includes. failEffect and worker fail callbacks can mark an attempt non-retryable or set retryAfterMs.
Co-authored-by: Cursor
---
.changeset/effect-reliability.md | 9 +
apps/docs/src/routes/docs.reliability.tsx | 8 +-
docs/production.md | 19 +-
packages/client/src/client.ts | 62 ++++++-
packages/core/src/effects.ts | 65 ++++++-
packages/core/src/fold.ts | 25 +++
packages/core/src/protocol.ts | 4 +
packages/core/src/state.ts | 5 +
packages/core/src/worker-callback.ts | 22 ++-
packages/runtime/src/dispatch-effect.ts | 66 +++++--
packages/runtime/src/helpers.ts | 9 +-
packages/runtime/src/index.ts | 1 +
packages/runtime/src/local-effect-executor.ts | 3 +
packages/runtime/src/looms.ts | 3 +-
packages/runtime/src/planners.test.ts | 6 +
packages/runtime/src/retry-policy.ts | 18 +-
packages/runtime/src/runtime.ts | 2 +-
packages/runtime/src/server/routes/effects.ts | 73 +++++++-
packages/runtime/src/types.ts | 14 +-
packages/runtime/src/worker-callbacks.ts | 170 +++++++++++-------
.../runtime/src/worker-effect-dispatch.ts | 2 +-
packages/runtime/src/worker-protocol.test.ts | 118 +++++++++++-
22 files changed, 593 insertions(+), 111 deletions(-)
create mode 100644 .changeset/effect-reliability.md
diff --git a/.changeset/effect-reliability.md b/.changeset/effect-reliability.md
new file mode 100644
index 0000000..5e278e9
--- /dev/null
+++ b/.changeset/effect-reliability.md
@@ -0,0 +1,9 @@
+---
+'@swirls/looms': minor
+---
+
+Worker callbacks now answer `{ status: 'accepted' | 'stale' | 'cancelled', cancelRequested }` instead of the run state. `createLooms().workerCallback` and `createLoomsClient().workerCallback` return that result. `cancelRequested` tells an attempt that is already running to stop, because a dispatch that has left the host cannot be recalled. A stale report records nothing.
+
+`failEffect({ error, nonRetryable, retryAfterMs })` fails the current attempt. `nonRetryable` ends the effect even when the retry policy has attempts left. `retryAfterMs` waits at least that long before the next attempt and never shortens the policy backoff. Worker `fail` callbacks take the same two fields.
+
+A heartbeat may include `details`. The latest details are kept across retries and included on the next `EffectWorkerTask`, so a resumed attempt can continue from the recorded progress.
diff --git a/apps/docs/src/routes/docs.reliability.tsx b/apps/docs/src/routes/docs.reliability.tsx
index 243e287..4ccc211 100644
--- a/apps/docs/src/routes/docs.reliability.tsx
+++ b/apps/docs/src/routes/docs.reliability.tsx
@@ -67,7 +67,13 @@ function Page() {
a restart preserves the retry budget and backoff. Configure start, heartbeat, and execution
timeouts for work delivered to external workers. Worker callbacks include the effect ID and
attempt number; stale callbacks cannot complete a newer attempt. Exhaustion records{' '}
- runtime.effect.failed.
+ runtime.effect.failed. A handler can stop that budget early with{' '}
+ failEffect and nonRetryable: true, or ask the next attempt to wait
+ at least retryAfterMs without shortening the policy backoff. A worker heartbeat
+ can carry details; the next dispatch of that effect includes the latest details
+ so the attempt can resume. The callback response is{' '}
+ {'{ status, cancelRequested }'}, not the run state.{' '}
+ cancelRequested tells an attempt that is already running to stop.