resonate-durable-sleep-scheduled-work-rust — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited resonate-durable-sleep-scheduled-work-rust (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.
SDK note (0.6.0). The Rust SDK is in active development. This skill covers two features in the current release:ctx.sleep(Duration)for in-workflow durable sleep, andresonate.schedule(name, cron, fn, input)for cron-registered ephemeral-world scheduling. (Note: Python'sresonate-sdk0.7.4 also has a top-levelresonate.schedule(...)now — confirmed against the installed package; see the cross-SDK asymmetry note below for what's still Rust/TypeScript-only.)
Two related but distinct capabilities:
ctx.sleep(Duration) pauses execution; the worker process is free to exit and resume days later without the sleep "losing its place."resonate.schedule(...) registers a function to fire on a cron schedule until explicitly deleted.Both are Rust-idiomatic — Duration for in-workflow time, cron strings for cron schedules. Both are durable; Resonate holds the continuation in its store, not in a long-running process.
ctx.sleep(Duration)use resonate::prelude::*;
use std::time::Duration;
#[resonate::function]
async fn daily_reminder(ctx: &Context, user_id: String) -> Result<()> {
loop {
ctx.sleep(Duration::from_secs(24 * 60 * 60)).await?; // 24 hours
ctx.rpc::<()>("send_reminder", user_id.clone()).await?;
}
}No upper bound on sleep duration. The worker process can exit after the .await? — Resonate's server holds the continuation and re-dispatches when the sleep expires.
#[resonate::function]
async fn seven_day_renewal_reminder(ctx: &Context, subscription_id: String) -> Result<()> {
// 7 days before renewal
ctx.sleep(Duration::from_secs(7 * 24 * 60 * 60)).await?;
ctx.rpc::<()>("send_renewal_warning", subscription_id.clone()).await?;
// 1 day before renewal
ctx.sleep(Duration::from_secs(6 * 24 * 60 * 60)).await?;
ctx.rpc::<()>("send_final_warning", subscription_id.clone()).await?;
// renewal day
ctx.sleep(Duration::from_secs(24 * 60 * 60)).await?;
ctx.rpc::<()>("charge_renewal", subscription_id).await?;
Ok(())
}Each ctx.sleep is a checkpoint; if the worker crashes during the 7-day sleep, the function resumes from the exact sleep on restart.
#[resonate::function]
async fn countdown_workflow(ctx: &Context, (name, minutes): (String, u32)) -> Result<String> {
for remaining in (1..=minutes).rev() {
ctx.run(send_tick, (name.clone(), remaining)).await?;
ctx.sleep(Duration::from_secs(60)).await?;
}
ctx.run(send_complete, name.clone()).await?;
Ok(format!("completed {}", name))
}
#[resonate::function]
async fn send_tick((name, remaining): (String, u32)) -> Result<()> {
// emit a webhook or log; side effect lives inside the leaf so it's checkpointed
Ok(())
}
#[resonate::function]
async fn send_complete(name: String) -> Result<()> { Ok(()) }The ctx.run(send_tick, ...) is durable; the subsequent ctx.sleep(...) is durable. A crash during the minute-long pause resumes mid-loop on restart.
#[resonate::function]
async fn birthday_greeting(ctx: &Context, (user_id, until): (String, Duration)) -> Result<()> {
ctx.sleep(until).await?; // sleep months/years
ctx.rpc::<()>("send_birthday_email", user_id).await?;
Ok(())
}Cost is roughly one promise-record for the duration; not tied to process uptime.
resonate.schedule(...)Scheduling is an ephemeral-world API — you register schedules at process start (or dynamically in response to admin actions), and Resonate dispatches invocations at the configured times.
use resonate::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let resonate = Resonate::local();
resonate.register(nightly_reconciliation).unwrap();
// cron: at 02:00 every day
let _schedule = resonate
.schedule(
"nightly-recon",
"0 2 * * *",
"nightly_reconciliation",
serde_json::json!({ "date": "auto" }),
)
.await?;
// keep the process alive; SDK dispatches invocations on schedule
tokio::signal::ctrl_c().await?;
resonate.stop().await?;
Ok(())
}
#[resonate::function]
async fn nightly_reconciliation(ctx: &Context, input: serde_json::Value) -> Result<()> {
// called daily at 02:00
Ok(())
}let schedule = resonate.schedule("temp-job", "*/15 * * * *", "cleanup", serde_json::json!({})).await?;
// ... later ...
schedule.delete().await?;Standard 5-field POSIX cron (minute hour day month dayofweek). The Resonate server evaluates the schedule and dispatches invocations to the registered function by name.
The SDK generates an invocation ID per schedule firing that's stable given the schedule + time. If the worker crashes during a scheduled run, the same ID is reused — the partial execution resumes; a duplicate schedule does not create a duplicate invocation.
Common pattern: a schedule fires a "daily digest" workflow; the workflow uses ctx.sleep to delay sub-steps:
// schedule fires daily at 09:00
let _digest_schedule = resonate
.schedule("morning-digest", "0 9 * * *", "daily_digest", serde_json::json!({}))
.await?;
#[resonate::function]
async fn daily_digest(ctx: &Context, _input: serde_json::Value) -> Result<()> {
// aggregate overnight
ctx.run(aggregate_overnight, ()).await?;
// small delay before sending to batch up late arrivals
ctx.sleep(Duration::from_secs(300)).await?;
ctx.rpc::<()>("send_digests_to_all_users", ()).await?;
Ok(())
}std::time::Duration, never a raw numberresonate.schedule(...) is the delete APIresonate.schedule(...) convenience wrapper. Go's 0.1.0 tag is the odd one out — it has the lower-level Schedules().Create sub-client (direct cron-fired promises) but not this same one-call function-dispatch wrapper yet. If you're porting a scheduled workflow to Go, see resonate-durable-sleep-scheduled-work-go for the manual dispatch-tag workaroundtokio::time::sleep(...) inside a durable function — that's NOT durable; use ctx.sleep(Duration::...)resonate.schedule() as "fire-and-forget register once, never delete" — leaked schedules invoke forever; keep handles or at least names you can revisitctx.sleep is durable but time drift between worker + server is a real failure mode; if a 24h sleep fires in 23h or 25h, that's within specresonate-basic-durable-world-usage-rust — ctx.sleep APIresonate-basic-ephemeral-world-usage-rust — resonate.schedule APIdurable-execution — foundational replay semantics; sleep + schedule are durability-centered by designresonate-durable-sleep-scheduled-work-typescript — sibling SDK for comparison (Python's resonate-sdk 0.7.4 has the equivalent resonate.schedule(...); see README)resonate-durable-sleep-scheduled-work-go — Go's 0.1.0 Schedules() sub-client is lower-level than this API; no top-level function-dispatch wrapper yet~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.