resonate-durable-sleep-scheduled-work-go — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited resonate-durable-sleep-scheduled-work-go (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.
Pre-release caveat. The Go SDK has no semver-tagged release yet, and — unlike the TypeScript/Rust SDKs — exposes no top-level `Schedule` API. This skill covers durable sleep (ctx.Sleep) and the available recurring-work patterns (in-workflowctx.Sleeploops; external cron →RPC). Every code block is verified againstdevelop/go.mdx,example-durable-sleep-go, andexample-countdown-goat SDK commit22076134651f.
Two related capabilities in the Go SDK:
ctx.Sleep(d time.Duration) pauses execution; the worker process can exit and resume later without losing its place. The server holds the timer promise; cost is one promise record, not process uptime.resonate.schedule(...) in Go yet. Use an in-workflow ctx.Sleep loop (bounded or long-running periodic task owned by one workflow) or an external cron that fires r.RPC(...) on each tick.Both patterns are durable: Resonate holds the continuation in its store, not in a long-running goroutine.
time.Sleep but need the work to survive a process restartctx.Sleep basicsctx.Sleep takes a time.Duration and returns a *resonate.Future. Call f.Await(nil) to suspend until the timer fires — there is no value to decode.
// From example-durable-sleep-go/main.go
func sleepingWorkflow(ctx *resonate.Context, args SleepArgs) (string, error) {
d := time.Duration(args.Secs) * time.Second
f, err := ctx.Sleep(d)
if err != nil {
return "", fmt.Errorf("ctx.Sleep: %w", err)
}
// Await(nil) — no value to decode; suspends until the timer promise resolves.
if err := f.Await(nil); err != nil {
return "", fmt.Errorf("sleep await: %w", err)
}
return fmt.Sprintf("slept for %d second(s)", args.Secs), nil
}Crash recovery. With a real Resonate server (-url=http://localhost:8001), killing the worker mid-sleep and restarting with the same promise ID resumes from the outstanding timer rather than restarting the workflow. The localnet transport runs state in process memory, so crash recovery requires a real server.
Duration encoding tip. time.Duration is int64 nanoseconds and round-trips through JSON as a bare number, which is opaque in promise payloads. Store durations as explicit seconds fields (as SleepArgs.Secs does) to keep stored promise data readable.
Sequential ctx.Sleep calls are independent durable checkpoints. A crash mid-sleep resumes from that exact sleep on restart — earlier sleeps that already settled are skipped.
// Three-phase renewal reminder: 7 days out, 1 day out, renewal day.
func renewalReminder(ctx *resonate.Context, subID string) (struct{}, error) {
// 7 days before renewal
if f, err := ctx.Sleep(7 * 24 * time.Hour); err != nil {
return struct{}{}, err
} else if err := f.Await(nil); err != nil {
return struct{}{}, err
}
if f, err := ctx.RPC("send-renewal-warning", subID); err != nil {
return struct{}{}, err
} else if err := f.Await(nil); err != nil {
return struct{}{}, err
}
// 6 more days (1 day before renewal)
if f, err := ctx.Sleep(6 * 24 * time.Hour); err != nil {
return struct{}{}, err
} else if err := f.Await(nil); err != nil {
return struct{}{}, err
}
if f, err := ctx.RPC("send-final-warning", subID); err != nil {
return struct{}{}, err
} else if err := f.Await(nil); err != nil {
return struct{}{}, err
}
// 1 more day — renewal day
if f, err := ctx.Sleep(24 * time.Hour); err != nil {
return struct{}{}, err
} else if err := f.Await(nil); err != nil {
return struct{}{}, err
}
f, err := ctx.RPC("charge-renewal", subID)
if err != nil {
return struct{}{}, err
}
return struct{}{}, f.Await(nil)
}The example-countdown-go canonical example shows the real in-workflow loop pattern: dispatch the side effect via ctx.RPC (durable, checkpointed), then ctx.Sleep between ticks.
// From example-countdown-go/main.go — adapted for clarity.
func countdown(ctx *resonate.Context, args CountdownArgs) (CountdownResult, error) {
sent := 0
for i := args.Start; i > 0; i-- {
// Side effect lives inside ctx.RPC so it's checkpointed — won't double-fire on resume.
f, err := ctx.RPC("notify", NotifyArgs{Count: i, URL: args.NotifyURL})
if err != nil {
return CountdownResult{}, err
}
var r NotifyResult
if err := f.Await(&r); err != nil {
return CountdownResult{}, fmt.Errorf("notify %d: %w", i, err)
}
sent++
if i > 1 {
s, err := ctx.Sleep(time.Duration(args.StepSeconds) * time.Second)
if err != nil {
return CountdownResult{}, err
}
if err := s.Await(nil); err != nil {
return CountdownResult{}, fmt.Errorf("sleep before %d: %w", i-1, err)
}
}
}
return CountdownResult{Sent: sent}, nil
}A crash during the ctx.Sleep between ticks resumes mid-loop — completed ctx.RPC ticks short-circuit on replay; the pending sleep re-suspends until its timer fires.
Sleep duration is unbounded. Cost is roughly one promise record; the process does not need to stay alive.
// Sleep for months — the process can exit and the timer holds in the server.
func birthdayGreeting(ctx *resonate.Context, args BirthdayArgs) (struct{}, error) {
f, err := ctx.Sleep(args.UntilBirthday) // weeks or months ahead
if err != nil {
return struct{}{}, err
}
if err := f.Await(nil); err != nil {
return struct{}{}, err
}
gf, err := ctx.RPC("send-birthday-email", args.UserID)
if err != nil {
return struct{}{}, err
}
return struct{}{}, gf.Await(nil)
}`resonate.schedule(...)` does not exist in the Go SDK. The go.mdx docs include an explicit callout: "No Schedule or top-level promises sub-client yet." Do not translate Rust or TypeScript schedule examples directly — the API is absent.
Two available substitutes:
ctx.Sleep loopA workflow that loops indefinitely (or for a bounded count) and sleeps between iterations is a self-contained recurring job. The loop is fully durable — a crash mid-sleep resumes at the current iteration.
// Periodic cleanup job: runs every intervalDays days, indefinitely.
func periodicCleanup(ctx *resonate.Context, args CleanupArgs) (struct{}, error) {
for {
// Side effect checkpointed in ctx.Run — won't double-fire on replay.
f, err := ctx.Run(runCleanup, args)
if err != nil {
return struct{}{}, err
}
if err := f.Await(nil); err != nil {
return struct{}{}, err
}
// Durable sleep until next run.
s, err := ctx.Sleep(time.Duration(args.IntervalDays) * 24 * time.Hour)
if err != nil {
return struct{}{}, err
}
if err := s.Await(nil); err != nil {
return struct{}{}, err
}
}
}Start once with a stable promise ID:
// Invoke from the ephemeral world — deduplicated on the ID, so safe to re-run on deploy.
h, err := cleanupFn.Run(ctx, "periodic-cleanup-prod", CleanupArgs{IntervalDays: 7})When to use: bounded or long-running periodic task owned by exactly one workflow; interval driven by business logic inside the workflow.
r.RPCKeep the schedule outside Resonate (OS cron, Cloud Scheduler, GitHub Actions, Kubernetes CronJob). Each tick calls r.RPC (or the resonate invoke CLI) to create a durable invocation.
// cron-trigger/main.go — runs on every cron tick; idempotent on stable ID.
func main() {
r, err := resonate.New(resonate.Config{URL: os.Getenv("RESONATE_URL")})
if err != nil {
log.Fatalf("resonate.New: %v", err)
}
defer func() { _ = r.Stop() }()
// Stable ID for today's run — deduplicates if the cron fires twice.
today := time.Now().UTC().Format("2006-01-02")
id := fmt.Sprintf("nightly-recon/%s", today)
ctx := context.Background()
h, err := r.RPC(ctx, id, "nightly-reconciliation", ReconArgs{Date: today})
if err != nil {
log.Fatalf("RPC: %v", err)
}
var result ReconResult
if err := h.Result(ctx, &result); err != nil {
log.Fatalf("Result: %v", err)
}
log.Printf("reconciliation done: %+v", result)
}When to use: the schedule already lives in an external system; per-firing invocations are independent (no loop state carried across ticks); or the interval must be changed without redeploying a long-running workflow.
CLI equivalent (no code trigger needed):
resonate invoke nightly-reconciliation --id "nightly-recon/$(date +%F)" --data '{"date":"2026-06-10"}'nil to Await (unlike ctx.Run/ctx.RPC futures where you decode into a pointer).24*time.Hour, time.Minute, etc.); no raw millisecond integers, no cron strings.time.Sleep inside a durable function is not durable (lost on crash, blocks the goroutine for its full duration). Always use ctx.Sleep for anything you need to survive a restart.ctx.Sleep takes only a time.Duration; no options struct. ctx.Run/ctx.RPC accept an optional trailing RunOpts/RPCOpts struct.localnet.NewLocal(...) has no HTTP endpoint; the default AsyncHeartbeat will error. Always pair localnet with Heartbeat: resonate.NoopHeartbeat{}.ctx.Sleep.ctx.Sleep (or any other durable boundary) re-executes on resume. Wrap observable side effects (DB writes, emails, webhooks) in ctx.Run/ctx.RPC so the durable promise records the result and short-circuits replay.resonate.schedule(...) code verbatim will not compile. Use the two patterns above.ctx.Sleep(24*time.Hour) firing in 23–25h is within spec (server/worker drift). Don't treat variance of ±1h as a bug for long-horizon sleeps.resonate-basic-durable-world-usage-go — ctx.Run, ctx.RPC, ctx.Promise fundamentals; the same Context the ctx.Sleep API lives ondurable-execution — foundational replay semantics; sleep is a durability checkpoint by designresonate-durable-sleep-scheduled-work-typescript — TypeScript sibling; has resonate.schedule() (cron strings, ms durations)resonate-durable-sleep-scheduled-work-rust — Rust sibling; has resonate.schedule() (cron strings, std::time::Duration)schedule() as of this writing; TypeScript and Rust have it. If porting a scheduled workflow from Rust/TypeScript to Go, replace resonate.schedule(...) with one of the two patterns in the "Scheduled / recurring work" section above.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.