Install
$ agentstack add skill-resonatehq-resonate-skills-resonate-migrate-from-temporal ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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/@activitysplit. 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 (helloworld, childworkflows, timerexamples, messagepassing, saga, continueasnew, …). 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.10.2 | npm | | Python | resonate-sdk v0.6.7 | PyPI | | Rust | resonate-sdk v0.4.0 | crates.io | | Go | pre-release, tracks main | 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=…) | yield ctx.run(fn, args) — 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.alloverexecuteChild/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)thenf.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.10.2):
resonate.promises.resolve(id, { data: Buffer.from(JSON.stringify(v)).toString("base64") }) - py (0.6.7):
resonate.promises.resolve(id=…, ikey=…) - rs (0.4.0):
resonate.promises.resolve(&id, Value::from_serializable(v)?)
(the example repo tracks SDK main and uses json!(v), which does NOT compile against the released v0.4.0 crate — use the Value form)
- go (main): no high-level resolve yet — use the CLI
resonate promises resolve --value '{"data":"…"}' or r.Sender().PromiseSettle(...) with a base64-encoded codec value (resonate-sdk-go#28).
- 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 ✅ (Go resolve is lower-level).
Pattern: Saga / compensation
- DETECT: a compensation list drained in
catch(ts), stackeddefer
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/forwithctx.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/decodeoverPayload[]) + 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/
(helloworld, childworkflows, timerexamples, messagepassing, saga, continueasnew, …); 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-executionandresonate-philosophyskills.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: resonatehq
- Source: resonatehq/resonate-skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.