@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 Sunday

Common Patterns

ExpressionDescription
*/15 * * * *Every 15 minutes
0 * * * *Every hour
0 9 * * *Daily at 9 AM
0 9 * * 1-5Weekdays at 9 AM
0 0 * * 0Weekly 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 UTC
async 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.