# How to Set Up Recurring Jobs with Cron Schedules in Agent-Native

> Easily set up recurring jobs with cron schedules in Agent-Native. Our pure-function wrapper validates and humanizes cron expressions for reliable task automation.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-07-02

---

**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`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main/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 next `Date` a cron expression should fire relative to the `after` parameter (defaults to `new Date()`). Internally, it normalizes the expression, calls `CronExpressionParser.parse`, and returns the result of `.next()`.
- **`isValidCron(cronExpr)`** – Validates syntax by attempting to parse the expression inside a `try/catch` block, 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`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main/templates/brain/jobs/process-ingest-queue.ts)—runs a continuous *tick* loop. On each iteration, the worker:

1. Queries the `jobs` table for entries where `nextRunAt` is less than or equal to the current time.
2. Invokes the target action using the Agent-Native action-dispatch API (`invokeAction`, `appAction`, etc.).
3. Recomputes the next fire time by calling `nextOccurrence(job.cron, now)` and updates the row’s `nextRunAt` column.

### 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.

```typescript
// 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`.

```typescript
// 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.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/jobs/cron.ts) wrap `cron-parser` with `nextOccurrence`, `isValidCron`, and `describeCron`, ensuring predictable, side-effect-free scheduling logic.
- **Normalization** handles legacy aliases like `@midnight` before parsing, preventing errors from upstream parser versions.
- **Job records** are stored in a SQL table with `nextRunAt`, `cron`, and `targetAction` columns, updated atomically by a background tick loop.
- **Validation** occurs at creation time via `isValidCron`, while `describeCron` powers administrative UI labels.

## Frequently Asked Questions

### How does Agent-Native handle invalid cron expressions?

The `isValidCron` function in [`packages/core/src/jobs/cron.ts`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main/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`](https://github.com/BuilderIO/agent-native/blob/main/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.