@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.
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:
// minute hour day month weekday
// * * * * *
@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
| 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:
@HyperfoldAgent({ name: 'maintenance-bot', type: 'custom' })
export class MaintenanceBot {
@OnSchedule('*/15 9-17 * * 1-5')
async frequentInventorySync() {
await this.catalog.syncWithShopify();
}
@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);
}
}
@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,
});
}
@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),
});
}
@OnSchedule('0 2 1 * *')
async monthlyReconciliation() {
const month = new Date().toISOString().slice(0, 7);
const usage = await this.billing.calculateMonthlyUsage(month);
await this.billing.generateInvoices(usage);
await this.notifications.sendEmail({
to: 'billing@company.com',
subject: `Monthly Billing: ${month}`,
data: usage,
});
}
@OnSchedule('0 3 * * *')
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
Default timezone is UTC. Specify timezone for business-hours tasks:
@OnSchedule('0 9 * * *') // 9 AM UTC
async task() { }
@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
}
Default timezone is UTC. Always specify timezone explicitly for business-hours tasks to avoid confusion.
Best Practices
Use locking for long-running tasks, handle failures gracefully, and use idempotent operations. Avoid assuming tasks run at exactly the scheduled time—Cloud Scheduler may have slight delays.
@HyperfoldAgent({ name: 'scheduled-ops', type: 'custom' })
export class ScheduledOps {
@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();
}
}
@OnSchedule('0 9 * * *')
async criticalTask() {
try {
await this.doCriticalWork();
} catch (error) {
await this.alerts.send({
severity: 'critical',
message: `Critical task failed: ${error.message}`,
});
throw error;
}
}
@OnSchedule('*/5 * * * *')
async syncData() {
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);
}
await this.state.set('last_sync_timestamp', new Date().toISOString());
}
}
Expose custom HTTP endpoints with @OnEndpoint.