How to Set Up Recurring Jobs with Cron Schedules in Agent-Native
Agent-Native provides a lightweight, pure-function wrapper around cron-parser that exposes nextOccurrence, isValidCron, and describeCron to validate, schedule, and humanize cron expressions without side effects.
The BuilderIO/agent-native repository includes a battle-tested job scheduling system for running background tasks on repeating intervals. Located in packages/core/src/jobs/cron.ts, the cron utilities normalize expressions, compute next fire times, and generate readable descriptions, enabling any action or plugin to enqueue recurring work safely.
Core Cron Utilities in Agent-Native
Agent-Native does not reimplement cron logic. Instead, it wraps the popular cron-parser library with three zero-dependency utility functions that are fully covered by unit tests in packages/core/src/jobs/cron.spec.ts.
The Three Primary Functions
The wrapper exports pure functions that can be called from server plugins, custom extensions, or administrative tools:
nextOccurrence(cronExpr, after?)– Computes the nextDatea cron expression should fire relative to theafterparameter (defaults tonew Date()). Internally, it normalizes the expression, callsCronExpressionParser.parse, and returns the result of.next().isValidCron(cronExpr)– Validates syntax by attempting to parse the expression inside atry/catchblock, returning a boolean without throwing.describeCron(cronExpr)– Generates human-readable descriptions such as “Every weekday at 9 AM” by splitting the expression into fields and matching common patterns.
Expression Normalization and Aliases
The wrapper includes an internal ALIAS_MAP that converts legacy shorthand like @midnight into standard cron syntax before parsing. According to the source code in packages/core/src/jobs/cron.ts, this ensures that cron-parser v5 misinterpretations are corrected before any date calculations occur.
How the Recurring Job Scheduler Works
Recurring jobs in Agent-Native follow a three-stage pipeline that keeps scheduling logic decoupled from business logic.
1. Job Definition and Persistence
An action creates a record in the jobs SQL table (defined in the core schema). Each row stores the raw cron expression, a targetAction name, an optional JSON payload, and a computed nextRunAt timestamp.
2. The Scheduler Tick Loop
A lightweight background worker—similar to the example in templates/brain/jobs/process-ingest-queue.ts—runs a continuous tick loop. On each iteration, the worker:
- Queries the
jobstable for entries wherenextRunAtis less than or equal to the current time. - Invokes the target action using the Agent-Native action-dispatch API (
invokeAction,appAction, etc.). - Recomputes the next fire time by calling
nextOccurrence(job.cron, now)and updates the row’snextRunAtcolumn.
3. Validation and UI Descriptions
Before persisting a job, the creation logic calls isValidCron to reject malformed expressions immediately. The describeCron utility surfaces friendly text in administrative dashboards, allowing operators to preview schedules before they are committed.
Implementing a Recurring Job
Below are concrete implementations for registering a new recurring job and processing it.
Defining the Job Creation Action
Use the core cron utilities to validate and describe the schedule before inserting the database record.
// src/actions/recurring.ts
import { defineAction } from '@agent-native/core/actions';
import {
isValidCron,
describeCron,
nextOccurrence,
} from '@agent-native/core/jobs/cron';
interface ReportPayload {
reportId: string;
}
export const createDailyReportJob = defineAction({
name: 'createDailyReportJob',
input: { reportId: 'string' },
async run({ reportId }) {
const cron = '0 9 * * *'; // 09:00 UTC every day
if (!isValidCron(cron)) {
throw new Error('Invalid cron expression');
}
const description = describeCron(cron); // "Every day at 9 AM"
const firstRun = nextOccurrence(cron);
await db.insert('jobs', {
cron,
targetAction: 'sendDailyReport',
payload: { reportId } as ReportPayload,
nextRunAt: firstRun,
description,
});
return { ok: true, nextRunAt: firstRun, description };
},
});
Building the Job Processor Worker
The worker queries due jobs, dispatches actions, and reschedules using nextOccurrence.
// src/server/jobs/worker.ts
import { db } from '@agent-native/server/db';
import { nextOccurrence } from '@agent-native/core/jobs/cron';
import { invokeAction } from '@agent-native/core/actions';
export async function processDueJobs() {
const now = new Date();
const dueJobs = await db.select('jobs', {
where: { nextRunAt: { lte: now } },
});
for (const job of dueJobs) {
await invokeAction(job.targetAction, job.payload);
const nextRun = nextOccurrence(job.cron, now);
await db.update('jobs', job.id, { nextRunAt: nextRun });
}
}
Deploy processDueJobs on a one-minute interval using a Nitro plugin, a Cloud Run scheduled task, or a setInterval in your server entry point.
Summary
- Pure utilities in
packages/core/src/jobs/cron.tswrapcron-parserwithnextOccurrence,isValidCron, anddescribeCron, ensuring predictable, side-effect-free scheduling logic. - Normalization handles legacy aliases like
@midnightbefore parsing, preventing errors from upstream parser versions. - Job records are stored in a SQL table with
nextRunAt,cron, andtargetActioncolumns, updated atomically by a background tick loop. - Validation occurs at creation time via
isValidCron, whiledescribeCronpowers administrative UI labels.
Frequently Asked Questions
How does Agent-Native handle invalid cron expressions?
The isValidCron function in packages/core/src/jobs/cron.ts wraps the parser in a try/catch block and returns false for any expression that cannot be parsed. This allows actions to reject malformed schedules before persisting them to the database, preventing silent failures in the worker loop.
Can I use descriptive labels like @midnight in Agent-Native cron jobs?
Yes. The cron wrapper maintains an internal ALIAS_MAP that converts common shorthands such as @midnight into standard cron syntax before calling CronExpressionParser.parse. This normalization happens transparently in packages/core/src/jobs/cron.ts.
Where should I deploy the job processor worker in an Agent-Native application?
The worker logic that calls processDueJobs can be deployed as a Nitro server plugin, a standalone Cloud Run service, or any Node.js runtime that supports setInterval. The reference implementation in templates/brain/jobs/process-ingest-queue.ts demonstrates the polling pattern used to query and dispatch due jobs.
Are the cron utilities in Agent-Native dependent on specific timezones?
The utilities rely on cron-parser and the JavaScript Date object, which default to the runtime’s system timezone unless specified otherwise. When computing nextOccurrence, the function uses the after parameter (or new Date()), so ensure your server environment is configured to UTC if you require consistent cross-region scheduling.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →