resonate-migrate-from-dbos — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited resonate-migrate-from-dbos (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A pattern-by-pattern playbook for porting a DBOS application to Resonate. Identify which DBOS construct each piece of the source uses, apply the matching transform, and reach for the linked per-SDK skill for idiomatic target code.
dbos-inc/dbos-demo-apps / dbos-transact-* file. If you can't find it, say so.
emit DBOS Rust. When migrating to Resonate Rust, map from the DBOS Python or TypeScript idiom.
function made durable by ctx.run. Don't invent decorators or a step type.
discipline carries to Resonate: do side-effecting work inside ctx.run so it is checkpointed, not re-executed on replay.
reference exists yet.
Resonate SDK surfaces drift between versions.
| SDK | Resonate (target) | DBOS (source) |
|---|---|---|
| TypeScript | @resonatehq/sdk v0.11.4 (npm) | @dbos-inc/dbos-sdk v4.19.8 (npm) |
| Python | resonate-sdk v0.7.4 (PyPI) | dbos v2.23.0 (PyPI) |
| Rust | resonate-sdk v0.6.0 (crates.io) | — (no DBOS Rust SDK) |
| Go | 0.1.0 (tag has no v prefix — go get github.com/resonatehq/[email protected]) | dbos-transact-golang v0.17.0 |
| DBOS | Resonate |
|---|---|
@DBOS.workflow() / DBOS.registerWorkflow(fn) / dbos.RegisterWorkflow(ctx, fn) | a registered function (@resonate.register, resonate.register("name", fn), #[resonate::function], resonate.Register(r, "name", fn)) |
@DBOS.step() / DBOS.runStep(fn) / dbos.RunAsStep(ctx, fn) | a plain function invoked via ctx.run(fn, args) |
DBOS.start_workflow(fn, ...) / DBOS.startWorkflow(fn)(...) / dbos.RunWorkflow(ctx, fn, args) (child workflow) | ctx.rpc("name", args) or ctx.run(fn, args) — invoke by registered name; recursion is trivial |
durable queue: DBOS.register_queue + DBOS.enqueue_workflow(...) then handle.get_result() (fan-out) | start each non-blocking (ctx.beginRun / ctx.rfi / .spawn() / ctx.RPC), then await each |
DBOS.recv(topic, timeout) (block for a message) | one latent durable promise: p = ctx.promise() then await p |
DBOS.send(destId, msg, topic) (deliver from outside) | resonate.promises.resolve(id, value) (HTTP-addressable from anywhere) |
DBOS.set_event(key, value) / DBOS.get_event(wfId, key) (publish/read status) | the promise's own resolved value is the status; read it by id |
DBOS.sleep(...) | ctx.sleep(duration) |
@DBOS.scheduled(cron) / DBOS.create_schedule(...) / dbos.WithSchedule(cron) | resonate.schedule(id, cron, fn) (ts/rust) or resonate.schedules.create(...) (py) |
| try/except + explicit undo step (no saga DSL) | inline ctx.run(undo, …) in the error branch, guarded by what completed |
systemDatabaseUrl Postgres (or Python SQLite default) | nothing — the Worker runs in-memory until you connect a Resonate Server |
@DBOS.workflow() + @DBOS.step() (py), DBOS.registerWorkflow +DBOS.runStep / @DBOS.workflow()/@DBOS.step() static-method decorators (ts), dbos.RegisterWorkflow + dbos.RunAsStep (go).
via ctx.run. Drop the decorator split and the systemDatabaseUrl config.
dbos-demo-apps/{python/dbos-app-starter,typescript/dbos-node-starter,golang/dbos-go-starter}.example-hello-world-{ts,py,rs,go}.resonate-basic-durable-world-usage-{typescript,python,rust,go}.DBOS.start_workflow(fn, ...) (py), DBOS.startWorkflow(fn)(...) (ts),dbos.RunWorkflow(ctx, fn, args) called inside a workflow (go).
function by name (ctx.rpc("name", args) / ctx.run(fn, args)). A function can recurse on itself — Resonate composes functions indefinitely. Optionally pin child ids with .options(id=…).
dbos-demo-apps/{python,golang}/widget-store (start_workflow/RunWorkflowof the dispatch workflow). DBOS does not ship a recursive example.
example-recursive-factorial-{ts,py,rs,go}.resonate-recursive-fan-out-pattern-{typescript,python,rust,go}.DBOS.register_queue(...) + a loop of DBOS.enqueue_workflow(queue, fn, x)then handle.get_result() (py), DBOS.startWorkflow(fn, { queueName })(x) then h.getResult() (ts), dbos.NewWorkflowQueue + dbos.RunWorkflow(..., dbos.WithQueue(q.Name)) then handle.GetResult() (go).
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.
debouncing. Resonate's fan-out primitive is parallelism + join, not a managed queue — if the source relies on per-queue flow control, plan how to reproduce it (e.g. a worker group plus application-level limiting) and say so.
(the process_tasks enqueue-N-then-get_result fan-out), dbos-transact-golang README (10-task fan-out). (dbos-demo-apps/python/queue-patterns shows fair-queue / rate-limit / debounce, not a plain fan-out.)
example-fan-out-fan-in-{ts,py,rs,go}.resonate-recursive-fan-out-pattern-{typescript,python,rust,go}.DBOS.sleep(seconds) (py), DBOS.sleep(ms) (ts), dbos.Sleep(ctx, duration) (go).ctx.sleep(duration). **Mind the units — they matchDBOS almost exactly:**
ctx.sleep(ms)). DBOS DBOS.sleep is also ms.ctx.sleep(secs)). DBOS DBOS.sleep is also seconds.time.Duration (ctx.Sleep(d) then f.Await(nil)). DBOS dbos.Sleep(ctx, d) is also time.Duration.std::time::Duration (ctx.sleep(Duration::from_secs(n)).await?).dbos-demo-apps/{python,golang}/widget-store (DBOS.sleep(1) /dbos.Sleep(ctx, time.Second)), dbos-demo-apps/typescript/widget-store/src/shop.ts (DBOS.sleep(1000)).
example-durable-sleep-{ts,py,rs,go}.resonate-durable-sleep-scheduled-work-{typescript,rust,go} (noPython variant yet — for Python durable-sleep, delegate to resonate-basic-durable-world-usage-python).
@DBOS.scheduled(cron) decorator over @DBOS.workflow() with a(scheduled, actual) datetime signature (py/ts), DBOS.create_schedule(...) / apply_schedules(...) (py/ts), dbos.WithSchedule(cron) registration option (go).
resonate.schedule(id, cron, fn, ...args)(ts/rust) or resonate.schedules.create(id=…, cron=…, promise_id=…, …) (py). The server fires the promise on the cron; the worker runs the registered function.
(* * * * * * = every second). Resonate takes a standard 5-field cron (* * * * * = every minute). Drop the leading seconds field when migrating, or a * * * * * * copied verbatim silently fires far more often than intended.
dbos-transact-py/tests/test_scheduler_decorator.py,dbos-transact-ts/tests/scheduler_decorator.test.ts, dbos-transact-golang README.
example-schedule-{ts,py,rs}.resonate-durable-sleep-scheduled-work-{typescript,rust,go}.example-schedule-go yet; Go's 0.1.0Schedules().Create covers the direct cron-fired-promise half, but there is no top-level resonate.Schedule(id, cron, fn, args) convenience wrapper yet — map by analogy, or set the resonate:target dispatch tag by hand, per resonate-durable-sleep-scheduled-work-go).
DBOS.recv(topic, timeout) + DBOS.send(destId, msg, topic) and/orDBOS.set_event(key, value) + DBOS.get_event(wfId, key) (py/ts/go; Go uses generic forms dbos.Recv[T] / dbos.Send / dbos.SetEvent / dbos.GetEvent[T]).
recv/set_event pair with a single latent durablepromise (p = ctx.promise(); await p). Surface p.id to whoever will resolve it. Replace the external DBOS.send(...) with resonate.promises.resolve(id, value). The promise's resolved value is the status — you don't need a separate get_event channel.
data field is base64-encoded JSON —const data = Buffer.from(JSON.stringify(v), "utf8").toString("base64"); resonate.promises.resolve(id, { data }) (the codec base64-decodes data, so a raw JSON.stringify(v) round-trips to garbage)
await resonate.promises.resolve(id, value) — positional idand a Value, not resolve(id=…, ikey=…); there is no ikey kwarg on Promises.resolve in this release. Confirm the exact call shape against example-human-in-the-loop-py before emitting.
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)
r.Promises().Resolve(ctx, id, v) — the direct Promises()sub-client handles the codec encoding for you. The CLI (resonate promises resolve <id> --value '{"headers":{},"data":"…"}') and the lower-level sender/promise-settle path remain available.
dbos-demo-apps/{python,typescript,golang}/widget-store,dbos-demo-apps/python/agent-inbox (richer HITL with recv + set_event).
example-human-in-the-loop-{ts,py,rs,go}.resonate-human-in-the-loop-pattern-{typescript,python,rust,go}.0.1.0 has a direct Promises().Resolve; the CLI and low-level Sender().PromiseSettle remain as alternates).undo step (e.g. undo_reserve_inventory() / undoSubtractInventory() / undoReserveInventory) in the failure branch. DBOS has no saga DSL.
ctx.run. On failure, run the undo inline inthe catch/error branch, guarded by which steps actually completed. Make compensations idempotent. Same shape as DBOS — no compensation stack, no helper.
dbos-demo-apps/{python,typescript,golang}/widget-store(checkout workflow), dbos-demo-apps/python/reliable-refunds-langchain.
example-saga-booking-ts, example-money-transfer-{py,rs}.resonate-saga-pattern-{typescript,python,rust,go}.@DBOS.kafka_consumer) are a DBOS built-in withno direct Resonate example. The closest Resonate shape is a worker that creates a promise keyed by the message's idempotency key, but there is no worked sample — say so.
queue feature. Resonate fan-out is parallelism + join only; reproduce limits at the application or worker-group level and flag the gap.
list_workflows,cancel, resume, fork) are DBOS features without a 1:1 Resonate analog.
example-schedule-go.example-money-transfer-go / example-saga-booking-go).dbos-demo-apps, dbos-transact-{py,ts,golang})durable-execution and resonate-philosophy skills.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.