cron-jobs

bởi rivet-dev

Durable cron jobs with Rivet Actors: schedule.after and schedule.at timers survive restarts and crashes, plus re-arming recurring jobs and idempotent handlers.

npx skills add https://github.com/rivet-dev/skills --skill cron-jobs

Cron Jobs and Scheduled Tasks

IMPORTANT: Before doing anything, you MUST read BASE_SKILL.md in this skill's directory. It contains essential guidance on debugging, error handling, state management, deployment, and project setup. Those rules and patterns apply to all RivetKit work. Everything below assumes you have already read and understood it.

Working Examples

If you need a reference implementation, read the raw working example code in these templates:

Patterns for running durable cron jobs and scheduled tasks on Rivet Actors. Actor schedules are persistent timers owned by the engine, so a job keeps its deadline through actor sleep, restarts, upgrades, deploys, and crashes.

Starter Code

Start from the working Scheduling example on GitHub. It implements a reminder service with one-shot timers, a React frontend, and live trigger events.

The Scheduling API

The full API is documented in Scheduling. There are two methods, both available on the actor context:

MethodBehavior
c.schedule.after(duration, actionName, ...args)Runs the named action after duration milliseconds.
c.schedule.at(timestamp, actionName, ...args)Runs the named action at an exact epoch timestamp in milliseconds.

Key properties:

  • Durable: The schedule is persisted by the engine and the timer survives actor sleep, restart, upgrade, and crash. If the actor is asleep at the deadline, the engine wakes it to run the action. See Lifecycle.
  • Plain actions as callbacks: The scheduled callback is an ordinary action on the same actor, invoked by name. Arguments after the action name are forwarded positionally, for example c.schedule.after(delayMs, "triggerReminder", reminder.id).
  • No cancellation API: Rivet does not currently support canceling a scheduled action. The pattern is a tombstone guard: remove the entry from state and have the scheduled action no-op when it cannot find its entry. The example's cancelReminder and triggerReminder actions implement exactly this.

Recurring Jobs Via Re-Arm

c.schedule is one-shot, so recurring jobs are built by having the scheduled action re-arm itself at the end of each run:

Arm the first run from onCreate or a setup action; after that, the action keeps the chain alive by rescheduling itself.

Re-arming with after measures the next run from the end of the current one, so the cadence drifts later by the job's runtime on every cycle. If runs must stay aligned to a fixed cadence, re-arm with c.schedule.at(c.state.lastRunAt + DAY_MS, "runReport") instead.

The Scheduling example itself only uses one-shot reminders. A real re-arm implementation lives in the idle world actor, where the collectProduction action credits production, updates lastCollectedAt, and calls a scheduleCollection helper that re-arms with c.schedule.after(delayMs, "collectProduction", { buildingId }). It also shows catch-up handling: if the action runs late, it computes how many whole intervals elapsed since the last run and credits them in one batch before re-arming.

For multi-step jobs that need retries and progress tracking inside a single run, consider Workflows instead of chaining schedules.

Durability Comparison

ApproachTimer DurabilityHorizontal ScalingDeploys And Restarts
setTimeout / setIntervalIn-process memory onlyEvery replica arms its own timer, so jobs run once per instanceAll pending timers are lost on restart or crash
node-cron and similar librariesIn-process memory onlyEvery instance runs the job unless you add external lockingSchedule resets on deploy; runs missed during downtime are skipped
External cron serviceLives outside your appNeeds a public HTTP endpoint plus its own dedupe and retry stateSurvives your deploys but is separate infrastructure to operate
Rivet Actor schedulingPersisted by the engine as a durable timerExactly one actor per key, so the timer is armed once rather than once per replicaSurvives actor sleep, restart, upgrade, and crash

Idempotency

A scheduled action can fire more than you expect: a crash between doing the work and re-arming can cause the action to run again, and because schedules cannot be cancelled, an action can fire for an entry that was already removed. Design handlers so a duplicate firing is harmless:

  • Store a run marker in state: Keep a lastRunAt timestamp or a sequence number in actor state and update it inside the action. On each firing, compute elapsed time since the marker and skip or batch accordingly. The idle world actor's collectProduction does this with lastCollectedAt and whole-interval batching.
  • Tombstone guard for cancelled entries: The example's triggerReminder looks the reminder up in c.state.reminders first and returns with a warning if it is gone, so a fire-after-cancel is a safe no-op.
  • Keep work and marker updates in the same action: Actor state writes are persisted with the action, so updating the marker in the same handler that does the work keeps the two consistent.

Topology

TopologyUse WhenExample Key
Singleton job actorOne global job such as a nightly report or cleanup passjob["daily-report"]
Actor per scheduled entityPer-user or per-resource timers such as reminders, trials, or billing periodsreminder[userId]

The Scheduling example uses a single shared reminderActor["main"] key for demo simplicity. For production reminder systems, prefer one actor per user so timers, state, and load are isolated per entity. See Keys.

Reminder Service Example

TopicSummary
SchedulingOne-shot timers armed with c.schedule.after(delayMs, "triggerReminder", reminder.id) or c.schedule.at(timestamp, "triggerReminder", reminder.id).
StateJSON state holding reminders and completedCount; the scheduled action mutates state when it fires.
EventstriggerReminder broadcasts a reminderTriggered event to all connected clients. See Events.
CancellationcancelReminder only removes the reminder from state; the scheduled action may still fire and no-ops via a state lookup guard.

Actors

  • Key: reminderActor["main"]
  • Responsibility: Stores reminders in persistent state, arms a future self-action per reminder via c.schedule, marks reminders completed when the scheduled action fires, and broadcasts reminderTriggered to connected clients.
  • Actions
    • scheduleReminder
    • scheduleReminderAt
    • triggerReminder
    • getReminders
    • cancelReminder
    • getStats
  • Queues
    • None
  • State
    • JSON
    • reminders
    • completedCount

Lifecycle

sequenceDiagram
	participant C as Client
	participant R as reminderActor
	participant E as Engine

	C->>R: scheduleReminder(message, delayMs)
	R->>E: schedule.after(delayMs, triggerReminder, id)
	Note over E: schedule persisted + alarm armed
	R-->>C: reminder
	Note over R: actor sleeps
	E->>R: alarm fires, actor wakes
	Note over R: triggerReminder(id) runs
	R-->>C: reminderTriggered event
	Note over R: recurring jobs re-arm here with schedule.after

Security Checklist

The example is intentionally open: any client can connect to the shared ["main"] key and schedule or cancel anything. Treat all of the following as required extensions for production:

  • Validate schedule inputs: Clamp delayMs and timestamp from clients. Reject negative delays, timestamps in the past, and absurdly far-future deadlines, and bound message or payload sizes.
  • Never schedule client-chosen actions: Expose specific actions like scheduleReminder that internally arm a fixed callback. Do not pass a client-supplied action name or unchecked args into c.schedule.
  • Authenticate and scope keys: Add connection authentication and use per-user actor keys instead of one global key, so users cannot read or cancel each other's schedules.
  • Prune completed entries: The example's reminders array grows without bound. Remove or archive completed entries so state stays small.
  • Use stable IDs: Generate entry IDs with crypto.randomUUID() rather than timestamp-plus-random strings.

Reference Map

Actors

Cli

Clients

Cookbook

Deploy

General

Self Hosting

Thêm skills từ rivet-dev

ai-agent
rivet-dev
Xây dựng backend cho AI agent với bộ nhớ liên tục: một Rivet Actor cho mỗi cuộc hội thoại, xử lý tin nhắn theo hàng đợi, và phản hồi LLM dạng stream dưới dạng sự kiện thời gian thực.
official
ai-agent-workspace
rivet-dev
Cung cấp cho mỗi tác nhân AI máy tính riêng của nó: một không gian làm việc liên tục với hệ thống tệp, tiến trình, shell, mạng và các phiên tác nhân trên một quy trình nhẹ…
official
chat-room
rivet-dev
Build a realtime chat room backend with Rivet Actors: one actor per room, SQLite-backed message history, and WebSocket broadcast to every connected client.
official
collaborative-text-editor
rivet-dev
Xây dựng backend cho trình soạn thảo văn bản cộng tác với Yjs CRDTs và Rivet Actors: các actor theo từng tài liệu chuyển tiếp đồng bộ hóa và cập nhật nhận thức, đồng thời lưu trữ ảnh chụp nhanh.
official
live-cursors
rivet-dev
Con trỏ trực tiếp và sự hiện diện đa người chơi với Rivet Actors: trạng thái con trỏ theo từng kết nối, cập nhật thời gian thực qua sự kiện hoặc WebSockets thô, và điều chỉnh tần suất.
official
per-tenant-database
rivet-dev
Cô lập dữ liệu đa người thuê với một Rivet Actor cho mỗi người thuê: khóa actor là id người thuê, do đó mỗi người thuê có tập dữ liệu và migrations riêng biệt.
official
rivetkit-client-javascript
rivet-dev
Máy khách JavaScript để kết nối với Rivet Actors qua kết nối không trạng thái hoặc có trạng thái. Hỗ trợ môi trường trình duyệt, Node.js và Bun với tính năng tự động phát hiện điểm cuối qua biến môi trường hoặc cấu hình tường minh. Cung cấp hai chế độ tương tác: gọi hành động không trạng thái cho các yêu cầu độc lập và kết nối có trạng thái với đăng ký sự kiện thời gian thực. Bao gồm truy cập HTTP và WebSocket cấp thấp cho các actor triển khai trình xử lý onRequest hoặc onWebSocket. Cung cấp mảng dựa trên hợp chất...
official
rivetkit-client-react
rivet-dev
Trình kết nối React để kết nối với Rivet Actors bằng hooks và quản lý trạng thái thời gian thực. Tạo hooks có kiểu với createRivetKit() và kết nối đến các phiên bản actor bằng useActor() với khóa và tham số tùy chọn. Đăng ký sự kiện actor với useEvent() và theo dõi vòng đời kết nối qua các trạng thái connStatus và error. Sử dụng createClient() cho các lệnh gọi một lần không trạng thái, các phương thức khám phá actor (get, getOrCreate, create, getForId), và truy cập HTTP/WebSocket cấp thấp. Hỗ trợ khóa mảng phức hợp...
official