A countdown timer that demonstrates durable sleep - long pauses without consuming resources, surviving crashes and resuming exactly where it left off.
Traditional countdown timers keep a process running continuously, wasting resources during sleep periods. If the process crashes, you lose the countdown state.
Resonate's durable sleep allows workflows to:
- Pause for extended periods (hours, days, weeks)
- Consume zero resources while sleeping
- Survive crashes and resume at the right time
- Send periodic notifications reliably
This pattern applies to:
- Scheduled reminders - Meeting notifications, task deadlines
- SLA monitors - Alert if response not received in time
- Rate limiting - Enforce delays between operations
- Periodic reports - Daily/weekly/monthly automation
- Countdown timers - Track time until events
def countdown(ctx: Context, count: int, interval, url: str):
for i in range(count, 0, -1):
# Send notification (durable)
yield ctx.run(notify, message=f"Countdown: {i}", url=url)
# Sleep for interval (process can exit here)
yield ctx.sleep(interval)
# Final notification
yield ctx.run(notify, message="Countdown complete", url=url)- Send notification -
ctx.run()executes and checkpoints the result - Sleep -
ctx.sleep()suspends the workflow, process can exit - Resume - Resonate wakes the workflow after the interval
- Repeat - Continue from checkpoint without re-sending notifications
If the process crashes during sleep:
- Workflow state is preserved in Resonate
- Resonate automatically resumes at the scheduled wake time
- No notifications are duplicated
- Countdown continues as if nothing happened
- Python 3.13+
- uv (Python package manager)
- ntfy.sh account or webhook URL
# Install dependencies
uv sync# Start the countdown worker
uv run python countdown.pyThen trigger a countdown via the Resonate API or another process:
# Start 10-minute countdown with 1-minute intervals
curl -X POST http://localhost:8001/promises \
-H "Content-Type: application/json" \
-d '{
"id": "countdown/demo",
"timeout": 36000000,
"data": {
"func": "countdown",
"args": [10, 60000, "https://ntfy.sh/your-topic"]
}
}'Parameters:
count: Number of countdown steps (10 = 10 notifications)interval: Milliseconds between steps (60000 = 1 minute)url: Webhook URL for notifications (ntfy.sh, Slack, etc.)
Each notification sends JSON:
{
"message": "Countdown: 7"
}Create a .env file:
RESONATE_URL=http://localhost:8001
RESONATE_TOKEN=your-token-here
NTFY_URL=https://ntfy.sh/your-topicThe example uses ntfy.sh by default, but works with any webhook:
Slack:
url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"Discord:
url = "https://discord.com/api/webhooks/YOUR/WEBHOOK"Custom API:
url = "https://api.example.com/notifications"- Start countdown with long interval:
# 5 steps, 30 seconds each
resonate.run("test/crash", countdown, 5, 30000, url)-
Wait for first notification
-
Kill the process (Ctrl+C)
-
Restart the process:
uv run python countdown.py- Observe: Countdown resumes at the correct step, no duplicates
# 24-hour countdown with hourly notifications
resonate.run("test/longrun", countdown, 24, 3600000, url)Process can stop/start freely. Notifications arrive on schedule.
countdown.py # Countdown workflow definition
pyproject.toml # Dependencies
ctx.sleep(milliseconds)suspends workflow- Process can exit, workflow state persists
- Resonate resumes at the right time
- No polling, no cron jobs needed
- Each notification uses a deterministic ID
- Replays don't duplicate notifications
ctx.run()checkpoints prevent re-execution
- Zero CPU/memory during sleep
- Scale to zero between notifications
- Pay only for active execution time
- Monitoring: Track notification delivery success
- Error handling: Add retry logic for webhook failures
- Timeouts: Set workflow timeout longer than total countdown
- Backpressure: Limit concurrent countdown workflows
- Observability: Log each notification for audit trail
- example-countdown-ts - TypeScript version
- example-durable-sleep-py - Basic sleep patterns
- example-durable-sleep-ts - Basic sleep patterns (TypeScript)