@OnSchedule
Run tasks on a recurring schedule using cron expressions.
Overview
The @OnSchedule decorator marks methods that run on a recurring schedule. Schedules are defined using standard cron syntax and executed via Google Cloud Scheduler.
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { HyperfoldAgent, OnSchedule } from '@hyperfold/actions-sdk'; @HyperfoldAgent({ name: 'ops-bot', type: 'custom' })export class OpsBot { @OnSchedule('0 9 * * *') // Every day at 9:00 AM async dailyReport() { const stats = await this.analytics.getDailyStats(); await this.notifications.sendSlack({ channel: '#sales-reports', message: `Daily Report: ${stats.conversions} conversions, $${stats.revenue}`, }); } @OnSchedule('0 * * * *') // Every hour async syncInventory() { await this.integrations.shopify.syncInventory(); } @OnSchedule('0 0 1 * *') // First day of each month async monthlyReconciliation() { await this.billing.reconcile(); }}Cron Syntax
Standard 5-field cron expressions:
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Cron format: minute hour day month weekday// ┌─────── minute (0-59)// │ ┌───── hour (0-23)// │ │ ┌─── day of month (1-31)// │ │ │ ┌─ month (1-12)// │ │ │ │ ┌ day of week (0-6, Sun=0)// │ │ │ │ │// * * * * * @OnSchedule('0 9 * * *') // 9:00 AM every day@OnSchedule('30 14 * * *') // 2:30 PM every day@OnSchedule('0 */4 * * *') // Every 4 hours@OnSchedule('*/15 * * * *') // Every 15 minutes@OnSchedule('0 9 * * 1') // 9:00 AM every Monday@OnSchedule('0 9 * * 1-5') // 9:00 AM weekdays@OnSchedule('0 0 1 * *') // Midnight on 1st of month@OnSchedule('0 0 * * 0') // Midnight every SundayCommon Patterns
| Expression | Description |
|---|---|
*/15 * * * * | Every 15 minutes |
0 * * * * | Every hour |
0 9 * * * | Daily at 9 AM |
0 9 * * 1-5 | Weekdays at 9 AM |
0 0 * * 0 | Weekly on Sunday |
0 0 1 * * | Monthly on the 1st |
Examples
Common scheduled task patterns:
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@HyperfoldAgent({ name: 'maintenance-bot', type: 'custom' })export class MaintenanceBot { // Inventory sync every 15 minutes during business hours @OnSchedule('*/15 9-17 * * 1-5') async frequentInventorySync() { await this.catalog.syncWithShopify(); } // Competitor price check every hour @OnSchedule('0 * * * *') async checkCompetitorPrices() { const products = await this.catalog.getAll(); for (const product of products) { const competitorPrice = await this.pricing.fetchCompetitorPrice(product.id); await this.pricing.updateCompetitorCache(product.id, competitorPrice); } } // Daily performance report at 9 AM @OnSchedule('0 9 * * *') async dailyPerformanceReport() { const yesterday = await this.analytics.getStats('yesterday'); await this.notifications.sendEmail({ to: 'team@company.com', subject: `Daily Report: ${yesterday.date}`, template: 'daily-report', data: yesterday, }); } // Weekly digest on Monday mornings @OnSchedule('0 9 * * 1') async weeklyDigest() { const weekStats = await this.analytics.getStats('last_7_days'); await this.notifications.sendSlack({ channel: '#leadership', blocks: this.formatWeeklyDigest(weekStats), }); } // Monthly billing reconciliation @OnSchedule('0 2 1 * *') // 2 AM on 1st of month async monthlyReconciliation() { const month = new Date().toISOString().slice(0, 7); // Calculate usage const usage = await this.billing.calculateMonthlyUsage(month); // Generate invoices await this.billing.generateInvoices(usage); // Send summary await this.notifications.sendEmail({ to: 'billing@company.com', subject: `Monthly Billing: ${month}`, data: usage, }); } // Cleanup old sessions nightly @OnSchedule('0 3 * * *') // 3 AM daily async cleanupSessions() { const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000); const deleted = await this.sessions.deleteOlderThan(cutoff); console.log(`Cleaned up ${deleted} expired sessions`); }}Timezone Handling
Schedule tasks in specific timezones:
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Default: UTC timezone@OnSchedule('0 9 * * *') // 9 AM UTCasync task() { } // Specify timezone@OnSchedule('0 9 * * *', { timezone: 'America/New_York' })async eastCoastMorning() { // Runs at 9 AM Eastern Time} @OnSchedule('0 9 * * *', { timezone: 'Europe/London' })async londonMorning() { // Runs at 9 AM London time} // Multiple schedules for global coverage@OnSchedule('0 9 * * *', { timezone: 'America/Los_Angeles' })@OnSchedule('0 9 * * *', { timezone: 'Europe/London' })@OnSchedule('0 9 * * *', { timezone: 'Asia/Tokyo' })async globalMorningReport() { // Runs at 9 AM in each timezone}Default timezone is UTC. Always specify timezone explicitly for business-hours tasks to avoid confusion.
Best Practices
Guidelines for reliable scheduled tasks:
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@HyperfoldAgent({ name: 'scheduled-ops', type: 'custom' })export class ScheduledOps { // Use locking for long-running tasks @OnSchedule('0 * * * *') async expensiveTask() { const lock = await this.locks.acquire('expensive-task', { ttl: 3600 }); if (!lock) { console.log('Task already running, skipping'); return; } try { await this.doExpensiveWork(); } finally { await lock.release(); } } // Handle failures gracefully @OnSchedule('0 9 * * *') async criticalTask() { try { await this.doCriticalWork(); } catch (error) { // Alert on failure await this.alerts.send({ severity: 'critical', message: `Critical task failed: ${error.message}`, }); // Re-throw to mark execution as failed throw error; } } // Use idempotent operations @OnSchedule('*/5 * * * *') async syncData() { // Process only items modified since last sync const lastSync = await this.state.get('last_sync_timestamp'); const items = await this.source.getModifiedSince(lastSync); for (const item of items) { await this.destination.upsert(item); // Idempotent upsert } await this.state.set('last_sync_timestamp', new Date().toISOString()); } // Avoid: Tasks that depend on specific time // @OnSchedule('0 9 * * *') // async badExample() { // const now = new Date(); // // Don't assume this runs at exactly 9:00:00 // // Cloud Scheduler may have slight delays // }}Expose custom HTTP endpoints with @OnEndpoint.