How to Set Up Recurring Jobs and Scheduled Tasks in Agent-Native

Agent-Native runs background work through recurring jobs registered as Nitro plugins using the registerJob function, with job implementations stored in templates/*/server/jobs/ and scheduled via cron expressions or fixed intervals.

Setting up recurring jobs and scheduled tasks in Agent-Native requires understanding its Nitro-based plugin architecture. The BuilderIO/agent-native repository provides a robust automation framework where background tasks are registered at server startup and persist across restarts. This guide walks through the core implementation patterns using the registerJob API and the scheduling utilities found in the templates and core packages.

Core Architecture of Recurring Jobs

Agent-Native implements recurring jobs as Nitro plugins that register async functions with the internal scheduler. According to the source code in .agents/skills/automations/recurring-jobs.ts, the framework requires two distinct components: a job implementation module containing the actual business logic, and a plugin file that calls registerJob to add the task to the dispatch core. The scheduler guarantees execution even after server restarts by storing job definitions in the persistent dispatch layer.

Step-by-Step: Creating a Recurring Job

Step 1: Implement the Job Logic

Create a TypeScript module under your template’s server/jobs/ directory that exports an async function. This function receives a context object containing the database connection, logger, and other services. In templates/clips/server/jobs/poll-calendars.ts, the implementation fetches external calendar data and upserts it into the local database:

// templates/clips/server/jobs/poll-calendars.ts
import { getDb } from '@agent-native/core/db'
import { googleCalendarClient } from '../lib/google-calendar-client'

export async function pollCalendars(ctx: any) {
  const db = getDb()
  const accounts = await db.selectFrom('calendar_accounts').selectAll().execute()

  for (const acct of accounts) {
    const events = await googleCalendarClient(acct).listEvents({
      timeMin: new Date().toISOString(),
      timeMax: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
    })
    
    await db
      .insertInto('calendar_events')
      .values(events.map(e => ({ ...e, accountId: acct.id })))
      .onConflictDoUpdate({ target: ['googleEventId'], set: { ...e } })
      .execute()
  }

  ctx.logger.info('Poll-calendars completed', { accounts: accounts.length })
}

Step 2: Register the Job in a Nitro Plugin

Create a plugin file under templates/*/server/plugins/ that imports the registerJob function from @agent-native/core/automation. The plugin executes when the Nitro server starts, adding your job to the scheduler with a specific cadence. The templates/clips/server/plugins/calendar-jobs.ts file demonstrates registering the calendar poll job to run every five minutes:

// templates/clips/server/plugins/calendar-jobs.ts
import { defineNitroPlugin } from '@nitrojs/core'
import { registerJob } from '@agent-native/core/automation'

export default defineNitroPlugin(() => {
  registerJob('poll-calendars', '*/5 * * * *', async (ctx) => {
    const { pollCalendars } = await import('../jobs/poll-calendars')
    await pollCalendars(ctx)
  })
})

Step 3: Configure the Schedule

The registerJob function accepts either a cron expression (e.g., */5 * * * * for every five minutes) or a fixed-rate interval measured in seconds. The first parameter is the unique job identifier, the second is the schedule string, and the third is the async handler function. The scheduler parses these configurations within the dispatch core and triggers execution according to the specified pattern.

Manual Triggering via Actions

For scenarios requiring manual execution or custom parameters, expose the job logic through the actions API using defineAction. This creates a typed interface that UI components or other services can invoke via useActionMutation. The following example from actions/calendar/poll.ts wraps the same job logic for on-demand execution:

// actions/calendar/poll.ts
import { defineAction } from '@agent-native/core/actions'

export const pollCalendarsNow = defineAction('pollCalendarsNow', {
  description: 'Trigger the calendar-poll job immediately',
  async run(_, ctx) {
    const { pollCalendars } = await import('../../templates/clips/server/jobs/poll-calendars')
    await pollCalendars(ctx)
    return { ok: true }
  },
})

Leveraging the Scheduling Package

For complex calendar-based operations, the packages/scheduling library provides server-side utilities that recurring jobs can import. The packages/scheduling/src/server/availability-engine.ts file exports functions like getAvailableSlots that compute time slots based on team availability. Jobs can also manipulate schedule definitions programmatically using utilities found in packages/scheduling/src/actions/update-schedule.ts, enabling dynamic modification of recurring intervals based on external calendar data.

Summary

  • Job implementations belong in templates/*/server/jobs/ as async functions accepting a context object with database and logger services.
  • Registration occurs in templates/*/server/plugins/ using defineNitroPlugin and the registerJob function from @agent-native/core/automation.
  • Scheduling supports standard cron expressions or fixed intervals, with persistence handled by the dispatch core across server restarts.
  • Manual triggers can be exposed via defineAction in the actions/ directory to create callable endpoints for immediate job execution.
  • Advanced utilities in packages/scheduling provide time-zone handling and availability calculations for calendar-centric automation.

Frequently Asked Questions

Where does Agent-Native store recurring job implementations?

Recurring job implementations are stored as TypeScript modules under templates/*/server/jobs/, such as templates/clips/server/jobs/poll-calendars.ts. These files export async functions that contain the actual business logic, database queries, and external API calls to be executed on each schedule tick.

How does Agent-Native ensure jobs persist after server restarts?

The framework stores job registrations in the dispatch core, which maintains persistent state independent of the server process. When the Nitro server restarts, plugins in templates/*/server/plugins/ re-register the jobs via registerJob, and the scheduler restores the execution timeline from its persistent store, guaranteeing that missed intervals are handled appropriately.

Can I trigger a recurring job manually instead of waiting for the schedule?

Yes, by exposing the job logic through an action using defineAction in the actions/ directory. This creates a typed API endpoint that can be invoked from UI components via useActionMutation or called directly from other server-side code, allowing immediate execution of the same logic contained in your scheduled job.

What is the difference between registerJob and defineAction?

registerJob is used within Nitro plugins to schedule background work that runs automatically on a timer (cron or fixed interval), while defineAction creates callable API endpoints for request/response interactions. Jobs registered with registerJob run autonomously in the background, whereas actions defined with defineAction must be explicitly invoked by a client or another service.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →