Quickstart | Get started with Resonate
Install the server and SDK and run your first Resonate workflow.
Install the server#
code
brew install resonatehq/tap/resonateInstall the SDK#
Requires Node.js 22 or later.
code
npm install @resonatehq/sdkThe Resonate Python SDK requires **Python ≥3.12**.
Legacy server required
The Python SDK (v0.6.7) currently requires the legacy Resonate server (Go-based). It is not yet compatible with the current-generation Rust server that resonate dev starts. See the Python SDK guide for legacy server setup.
code
pip install resonate-sdkCargo.toml
[dependencies]
resonate = { package = "resonate-sdk", git = "https://github.com/resonatehq/resonate-sdk-rs", branch = "main" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }Write a function#
A countdown loop that survives restarts — minutes, hours, or days.
countdown.ts
import { Resonate, type Context } from "@resonatehq/sdk";
function* countdown(context: Context, count: number, delay: number) {
for (let i = count; i > 0; i--) {
// Run a function, persist its result
yield* context.run((context: Context) => console.log(`Countdown: ${i}`));
// Sleep
yield* context.sleep(delay * 1000);
}
console.log("Done!");
}
// Instantiate Resonate
const resonate = new Resonate({ url: "http://localhost:8001" });
// Register the function
resonate.register(countdown);countdown.py
from resonate import Resonate, Context
from threading import Event
def countdown(ctx: Context, count: int, delay: int):
for i in range(count, 0, -1):
# Run a function, persist its result
yield ctx.run(ntfy, i)
# Sleep
yield ctx.sleep(delay)
print("Done!")
def ntfy(_: Context, i: int):
print(f"Countdown: {i}")
# Instantiate Resonate
resonate = Resonate.remote()
# Register the function
resonate.register(countdown)
resonate.start() # Start Resonate threads
Event().wait() # Keep the main thread alivesrc/main.rs
use resonate::prelude::*;
use std::time::Duration;
#[resonate::function]
async fn countdown(ctx: &Context, count: i32, delay: u64) -> Result<()> {
for i in (1..=count).rev() {
ctx.run(notify, i).await?;
ctx.sleep(Duration::from_secs(delay)).await?;
}
println!("Done!");
Ok(())
}
#[resonate::function]
async fn notify(i: i32) -> Result<()> {
println!("Countdown: {i}");
Ok(())
}
#[tokio::main]
async fn main() {
let resonate = Resonate::new(ResonateConfig {
url: Some("http://localhost:8001".into()),
..Default::default()
});
resonate.register(countdown).unwrap();
resonate.register(notify).unwrap();
// Keep the process alive to receive work
tokio::signal::ctrl_c().await.unwrap();
}Start the server#
code
resonate devStart the worker#
code
npx ts-node countdown.tscode
python countdown.pycode
cargo runRun the function#
Run the function with execution ID countdown.1:
code
resonate invoke countdown.1 --func countdown --arg 5 --arg 60Result#
code
npx ts-node countdown.ts
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!code
python countdown.py
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!code
cargo run
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!What to try#
Inspect the execution with resonate tree — it renders the call graph as durable promises.
code
resonate tree countdown.1Now kill the worker mid-countdown and restart. The countdown resumes where it stopped.