# Resonate Migrate From Temporal

> Port a Temporal application to Resonate, pattern by pattern. Use when migrating Temporal Workflows/Activities, Signals, timers, sagas, continue-as-new loops, fan-out/fan-in, child workflows, distributed mutex, or encryption to the Resonate SDK. Maps canonical temporalio/samples-* examples to their Resonate equivalents across TypeScript, Python, Rust, and Go, with per-SDK API notes and honest coverage gaps. Foundational skill — delegates to the per-SDK pattern skills for idiomatic target code.

- Skill: `resonatehq/resonate-migrate-from-temporal` (Agent Skill)
- Install (CLI): `npx skillmds@latest add resonatehq/resonate-migrate-from-temporal`
- Raw SKILL.md: https://api.skillmd.com/api/skills/resonatehq/resonate-migrate-from-temporal/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: Apache-2.0
- Author: resonatehq (https://skillmd.com/u/resonatehq)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/resonatehq/resonate-migrate-from-temporal

---


# Migrate from Temporal to Resonate

A pattern-by-pattern playbook for porting a Temporal application to Resonate.
Identify which Temporal construct each piece of the source uses, apply the
matching transform, and reach for the linked per-SDK skill for idiomatic target
code.

## Ground rules

- **Never invent Temporal code.** Quote it from the user's source or a named
  `temporalio/samples-*` file. If you can't find it, say so.
- **Resonate has no `@workflow`/`@activity` split.** A step is just a function
  made durable by `ctx.run`. Don't invent decorators.
- **Temporal Rust samples live in-repo.** Temporal's Rust SDK ships examples in
  `temporalio/sdk-rust/crates/sdk/examples/` (no separate `samples-rust` repo) —
  source Rust migrations from there (hello_world, child_workflows, timer_examples,
  message_passing, saga, continue_as_new, …). Only mutex and encryption have no
  Temporal Rust example; for those, source from the Temporal *TypeScript* idiom.
- **State coverage honestly.** A missing example ≠ impossible; it means no worked
  reference exists yet.
- **Verify APIs against the pinned version before emitting** — the SDK surface
  drifts between versions.

## Pinned SDK versions (latest released at time of writing)

| SDK | Version | Source |
|---|---|---|
| TypeScript | `@resonatehq/sdk` v0.11.4 | npm |
| Python | `resonate-sdk` v0.7.4 | PyPI |
| Rust | `resonate-sdk` v0.6.0 | crates.io |
| Go | `0.1.0` (tag has no `v` prefix — `go get github.com/resonatehq/resonate-sdk-go@0.1.0`) | GitHub |

## Core mappings (apply everywhere)

| Temporal | Resonate |
|---|---|
| `@workflow.defn` / Workflow type | a registered function (`@resonate.register`, `resonate.register("name", fn)`, `#[resonate::function]`, `resonate.Register(r, "name", fn)`) |
| `@activity.defn` / Activity | a plain function invoked via `ctx.run(fn, args)` |
| `workflow.execute_activity(fn, …, start_to_close_timeout=…)` | `await ctx.run(fn, args)` (py, ts async engine) / `yield* ctx.run(fn, args)` (ts generator engine) / `ctx.run(fn, args).await?` (rs) / `ctx.Run(fn, args)` then `f.Await(&out)` (go) — no timeout policy required |
| Task Queue + Worker wiring | a worker `group` (only when you need distributed dispatch) |
| `executeChild` / `ExecuteChildWorkflow(ChildType, …)` | `ctx.rpc("name", args)` or `ctx.run(fn, args)` — invoke by registered name; recursion is trivial |
| `Promise.all` / `asyncio.gather` / parallel futures (fan-out) | start each non-blocking (`ctx.beginRun` / `ctx.rfi` / `.spawn()` / `ctx.RPC`), then await each |
| `defineSignal` + `setHandler` + `condition` | one latent durable promise: `p = ctx.promise()` then await `p` |
| `defineQuery` / query handler | delete — promise/result state is the source of truth |
| `handle.signal(sig)` (external) | `resonate.promises.resolve(id, value)` (HTTP-addressable from anywhere) |
| `workflow.sleep` / `NewTimer` | `ctx.sleep(duration)` |
| saga compensation stack + drain-on-catch | inline `ctx.run(undo, …)` in the error branch, guarded by what completed |
| `continueAsNew` | bounded loop: a plain loop. Unbounded loop: `ctx.detached(self, n+1)` tail-recursion |

---

## Pattern: Workflow + activity

- **DETECT:** `@workflow.defn`/`@activity.defn` (py), `proxyActivities` (ts),
  `RegisterWorkflow`+`RegisterActivity` / `workflow.ExecuteActivity` (go).
- **TRANSFORM:** Register one function. Turn each activity into a plain function
  called via `ctx.run`. Drop Task Queue wiring and `start_to_close_timeout`.
- **TEMPORAL SOURCE:** `samples-{python,typescript,go}/hello-world`
  (py: `hello/hello_activity.py`).
- **RESONATE TARGET:** `example-hello-world-{ts,py,rs,go}`.
- **RELATED SKILL:** `resonate-basic-durable-world-usage-{typescript,python,rust,go}`.
- **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅.

## Pattern: Composing functions (child workflows)

- **DETECT:** `executeChild` (ts), `workflow.ExecuteChildWorkflow` (go),
  `workflow.execute_child_workflow` (py).
- **TRANSFORM:** Replace the separate child Workflow type with a call to a
  registered function by name (`ctx.rpc("name", args)` / `ctx.run(fn, args)`). A
  function can recurse on itself. Optionally pin child ids with `.options(id=…)`.
- **TEMPORAL SOURCE:** `samples-typescript/child-workflows`,
  `samples-go/child-workflow` (no `samples-python` child-workflow example).
- **RESONATE TARGET:** `example-recursive-factorial-{ts,py,rs,go}`.
- **RELATED SKILL:** `resonate-recursive-fan-out-pattern-{typescript,python,rust,go}`.
- **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅.

## Pattern: Fan-out / fan-in (parallel + join)

- **DETECT:** `Promise.all` over `executeChild`/activities (ts), `asyncio.gather`
  (py), multiple `workflow.ExecuteActivity` futures then `.Get()` (go).
- **TRANSFORM:** Start each unit non-blocking — `ctx.beginRun` (ts) / `ctx.rfi`
  (py) / `ctx.run(...).spawn()` (rs) / `ctx.RPC` (go); each returns a future
  immediately. Then await each future. Start ALL before awaiting ANY, or the
  work serializes.
- **TEMPORAL SOURCE:** `samples-typescript/child-workflows` (`Promise.all`),
  `samples-python/hello/hello_parallel_activity.py`, `samples-go/splitmerge-future`.
- **RESONATE TARGET:** `example-fan-out-fan-in-{ts,py,rs,go}`.
- **RELATED SKILL:** `resonate-recursive-fan-out-pattern-{typescript,python,rust,go}`.
- **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅.

## Pattern: Durable timers

- **DETECT:** `workflow.sleep(timedelta)` (py), `sleep('30 days')` (ts),
  `workflow.NewTimer` (go).
- **TRANSFORM:** Replace with `ctx.sleep(duration)`. **Mind the units:**
  - TypeScript: **milliseconds** (`ctx.sleep(ms)`).
  - Python: **seconds** as float (`ctx.sleep(secs)`).
  - Go: `time.Duration` (`ctx.Sleep(d)` then `f.Await(nil)`).
  - Rust: `std::time::Duration` (`ctx.sleep(Duration::from_secs(n)).await?`).
- **TEMPORAL SOURCE:** `samples-{python,typescript,go}/sleep-for-days`.
- **RESONATE TARGET:** `example-durable-sleep-{ts,py,rs,go}`.
- **RELATED SKILL:** `resonate-durable-sleep-scheduled-work-{typescript,rust,go}`.
- **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅.

## Pattern: Signals → durable promises (human-in-the-loop)

- **DETECT:** `defineSignal`/`setHandler`/`condition`/`defineQuery` (ts),
  `@workflow.signal`/`workflow.wait_condition` (py),
  `workflow.GetSignalChannel`/`workflow.Await`/`Selector` (go).
- **TRANSFORM:** Replace the signal definition + handler + flag + condition with a
  single latent durable promise (`p = ctx.promise()`; await `p`). Surface `p.id`
  to whoever will resolve it (email/webhook/log). Replace `handle.signal(...)`
  with `resonate.promises.resolve(id, value)`. Delete Query handlers.
- **RESOLVE API — verify against pinned version:**
  - ts (0.11.4): `resonate.promises.resolve(id, { data: Buffer.from(JSON.stringify(v)).toString("base64") })`
  - py (0.7.4): `await resonate.promises.resolve(id, value)` — positional `id` and a
    `Value`, not `resolve(id=…, ikey=…)`; there is no `ikey` kwarg on `Promises.resolve`
    in this release.
  - rs (0.6.0): `resonate.promises.resolve(&id, Value::from_serializable(v)?)`
    (the example repo may use `json!(v)` — verify it compiles against your
    released crate version; use the `Value` form if not)
  - go (0.1.0): `r.Promises().Resolve(ctx, id, v)` — the direct `Promises()` sub-client
    handles the codec encoding for you. The CLI (`resonate promises resolve <id> --value '{"data":"…"}'`)
    and the low-level `r.Sender().PromiseSettle(...)` (manual base64-encoded codec value)
    remain available for cross-process or non-Go settlement.
- **TEMPORAL SOURCE:** `samples-typescript/signals-queries`,
  `samples-python/hello/hello_signal.py`, `samples-go/await-signals`.
- **RESONATE TARGET:** `example-human-in-the-loop-{ts,py,rs,go}`.
- **RELATED SKILL:** `resonate-human-in-the-loop-pattern-{typescript,python,rust,go}`.
- **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅ (`0.1.0` has a direct `Promises().Resolve`; the CLI and low-level `Sender().PromiseSettle` remain as alternates).

## Pattern: Saga / compensation

- **DETECT:** a compensation list drained in `catch` (ts), stacked `defer`
  compensations (go), handler-coordinated compensation (py).
- **TRANSFORM:** Run each step with `ctx.run`. On failure, run the undo inline in
  the `catch`/error branch, guarded by which steps actually completed. No
  compensation stack, no drain helper. Make compensations idempotent.
- **TEMPORAL SOURCE:** `samples-typescript/saga`, `samples-go/saga`,
  `samples-python/message_passing/waiting_for_handlers_and_compensation`.
- **RESONATE TARGET:** `example-saga-booking-ts`, `example-money-transfer-{py,rs}`.
- **RELATED SKILL:** `resonate-saga-pattern-{typescript,python,rust,go}`.
- **COVERAGE:** ts ✅ py ✅ rs ✅ go ⚠️ (no Go example yet — map by analogy).

## Pattern: Long-running loops

- **DETECT:** `continueAsNew` (ts), `workflow.NewContinueAsNewError` (go),
  `workflow.continue_as_new` (py).
- **TRANSFORM:**
  - Bounded loop → plain `while`/`for` with `ctx.run` + `ctx.sleep`. No
    `continueAsNew` equivalent needed.
  - **Truly unbounded loop → `ctx.detached(self, n+1)` tail-recursion**, split
    *inside* the per-iteration function. A naive infinite loop in a single durable
    invocation accumulates child promises that get re-walked on replay; once
    replay time exceeds the task lease, the worker loop stalls. Do not emit a naive
    unbounded loop (`while(true)` / `while True:` / `loop {}`) for genuinely
    infinite loops.
- **TEMPORAL SOURCE:** `samples-typescript/continue-as-new`,
  `samples-go/child-workflow-continue-as-new`,
  `samples-python/hello/hello_continue_as_new.py`.
- **RESONATE TARGET:** `example-infinite-workflow-{ts,go}`.
- **COVERAGE:** ts ✅ go ✅ py ⚠️ rs ⚠️ (no py/rs example yet — map by analogy).

## Pattern: Distributed mutex (TypeScript only)

- **DETECT:** a lock-manager workflow with a signal queue, `uuid4()` release
  tokens, and `continueAsNew`.
- **TRANSFORM:** Delete the lock machinery. Sequential `yield* ctx.run()` calls in
  a generator are serialized by the runtime — the generator is the lock. No
  signals, no tokens, no deadlock surface.
- **TEMPORAL SOURCE:** `samples-typescript/mutex`.
- **RESONATE TARGET:** `example-distributed-mutex-ts`.
- **COVERAGE:** ts ✅ (TypeScript only).

## Pattern: Encryption (TypeScript only)

- **DETECT:** a `PayloadCodec` (`encode`/`decode` over `Payload[]`) + custom
  `DataConverter` + codec server.
- **TRANSFORM:** Replace with a single `Encryptor` (`encrypt(Value): Value` /
  `decrypt(Value): Value`) passed as a constructor option:
  `new Resonate({ encryptor })`. Workflow code is unchanged; the SDK handles
  promise-store serialization.
- **TEMPORAL SOURCE:** `samples-typescript/encryption`.
- **RESONATE TARGET:** `example-encryption-ts`.
- **COVERAGE:** ts ✅ (TypeScript only).

---

## Coverage gaps (do not fabricate)

- Saga: no Go example (`example-saga-booking-go` / `example-money-transfer-go`).
- Long-running loops: no Python or Rust example (`example-infinite-workflow-py` / `-rs`).
- Distributed mutex, Encryption: TypeScript only.
- Rust source: Temporal Rust examples are in `temporalio/sdk-rust/crates/sdk/examples/`
  (hello_world, child_workflows, timer_examples, message_passing, saga,
  continue_as_new, …); only mutex and encryption have no Temporal Rust example.

## Source of truth

- Resonate examples: https://github.com/resonatehq-examples
- Temporal samples: https://github.com/temporalio (`samples-typescript`, `samples-python`, `samples-go`)
- Side-by-side guide: https://docs.resonatehq.io/evaluate/coming-from/temporal
- Concepts first: see the `durable-execution` and `resonate-philosophy` skills.

