mcpField
B
boxlang-scheduled-tasks

boxlang-scheduled-tasks

github.com/ortus-boxlangDevelopment041 installsUpdated 1w ago
Repository

About boxlang-scheduled-tasks

A BoxLang skill for scheduling in-process code and persistent HTTP-triggered tasks. It describes scheduler registration, lifecycle controls, timing patterns, and cluster-aware execution for BoxLang applications.

Key features

  1. 01Register scheduler classes in Application.bx
  2. 02Schedule callback tasks with a fluent DSL
  3. 03Manage scheduler lifecycle with BIFs
  4. 04Persist and control HTTP-triggered tasks
  5. 05Configure cron and calendar frequencies
  6. 06Run tasks on one cluster server

Use cases

  1. 01Schedule recurring maintenance callbacks
  2. 02Trigger webhooks or operational endpoints
  3. 03Inspect or control scheduler instances
  4. 04Avoid duplicate task runs across cluster nodes

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/ortus-boxlang/skills --skill boxlang-scheduled-tasks

After installing, type/boxlang-scheduled-tasksin Claude Code to confirm it loaded.

Details

From the project README

BoxLang Scheduled Tasks

BoxLang is the AI-native software productivity platform for building, modernizing and running applications, with developers and AI agents working together.

Overview

BoxLang offers two scheduling models:

  1. Scheduler DSL (in-process code execution) using scheduler classes backed by BaseScheduler and ScheduledTask
  2. bx:schedule component (HTTP-driven tasks) for create/update/delete/pause/resume/list/run task endpoints

Use the Scheduler DSL for rich in-process logic and task callbacks. Use bx:schedule for simple persistent URL triggers.

Application.bx Integration

You can register scheduler classes directly in Application.bx so they are auto-discovered for the app context.

For full descriptor behavior (app discovery, multi-app isolation, lifecycle), see application-descriptor.

class {

    this.name = "MyApp"

    // Auto-register scheduler classes for this application
    this.schedulers = [
        "schedulers.DailyMaintenance",
        "schedulers.HourlyReports"
    ]

}

Use this when scheduler lifecycle should be managed with application startup/shutdown rather than ad-hoc schedulerStart() calls.

Option 1: Scheduler DSL (Recommended for Code Workloads)

A scheduler class wraps BaseScheduler and registers tasks fluently in configure().

class {

    property name="scheduler"
    property name="logger"

    function configure() {
        scheduler
            .setSchedulerName( "reporting-scheduler" )
            .setTimezone( "America/Chicago" )

        scheduler.task( "daily-report", "reports" )
            .call( () => generateDailyReport() )
            .everyDayAt( "02:00" )
            .onFailure( ( task, exception ) => {
                logger.error( "Task #task.getName()# failed: #exception.message#" )
            } )
    }

}
Registering and Starting Scheduler Classes
// Start from class path (creates, configures, registers, and starts)
schedulerStart( className = "schedulers.ReportingScheduler", name = "reporting", force = true )

// Lookup / control
schedulerGet( "reporting" )
schedulerStats( "reporting" )
schedulerRestart( "reporting", force = false, timeout = 30 )
schedulerShutdown( "reporting", force = false, timeout = 30 )

Option 2: bx:schedule Component (HTTP-Driven)

bx:schedule persists task definitions (default: ${boxlang-home}/config/tasks.json) and executes scheduled HTTP GET requests.

bx:schedule action="create"
    task="nightlyCleanup"
    url="https://myapp.example/tasks/cleanup"
    cronTime="0 2 * * *";

bx:schedule action="pause" task="nightlyCleanup";
bx:schedule action="run" task="nightlyCleanup";
bx:schedule action="delete" task="nightlyCleanup";

Use this approach for webhooks, callback endpoints, and operational pings.

Scheduler BIFs

Core scheduler lifecycle BIFs:

  • schedulerStart( className, name?, force=true )
  • schedulerGet( name )
  • schedulerGetAll()
  • schedulerList()
  • schedulerStats( name? )
  • schedulerRestart( name, force=false, timeout=30 )
  • schedulerShutdown( name, force=false, timeout=30 )

Notes:

  • schedulerStart() instantiates the BoxLang class, wraps it in BoxScheduler, calls configure(), then registers/starts it.
  • schedulerRestart() and schedulerShutdown() validate scheduler existence and throw if missing.

Task Registration Patterns

Use meaningful names and optional groups for operational clarity.

scheduler.task( "cleanup-expired-sessions", "maintenance" )
    .call( () => cleanupSessions() )
    .every( 15, "minutes" )

scheduler.task( "send-weekly-digest", "notifications" )
    .call( () => sendDigest() )
    .everyWeekOn( "Monday", "08:30" )

Disable-at-registration for debugging:

scheduler.xtask( "experimental-task", "debug" )
    .call( () => runExperiment() )
    .everyMinute()

Common Frequency APIs

  • every( period, timeUnit )
  • spacedDelay( delay, timeUnit ) (no overlap style)
  • cron( expression )
  • everySecond(), everyMinute(), everyHour(), everyHourAt( minute )
  • everyDay(), everyDayAt( "HH:mm" )
  • everyWeek(), everyWeekOn( day, time )
  • everyMonth(), everyMonthOn( day, time )
  • everyYear(), everyYearOn( month, day, time )
  • onWeekdays( time ), onWeekends( time )

everyMonthOn( day ) clamps to the last day of shorter months (v1.18+), so everyMonthOn( 31 ) runs once in February instead of every day.

Server Fixation (Clusters, v1.18+)

When the same scheduler is deployed to every node, each node runs every task. Call .onOneServer() so only one node runs each execution. BoxLang uses the scheduler's cacheName cache as a distributed lock, so point it at a cache the servers share (for example Redis via bx-redis).

task( "nightly-cleanup" )
    .call( () => cleanupOldRecords() )
    .everyDayAt( "02:00" )
    .onOneServer()

Time units supported in scheduler APIs include: days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds.

Cron Support

Supported formats:

  • 5-field Unix: minute hour day-of-month month day-of-week
  • 6-field Quartz: second minute hour day-of-month month day-of-week

Examples:

// Every day at 2:00 AM
scheduler.task( "nightly" )
    .call( () => nightlyJob() )
    .cron( "0 2 * * *" )

// Every weekday at 8:30:00 AM
scheduler.task( "weekday-digest" )
    .call( () => digestJob() )
    .cron( "0 30 8 * * MON-FRI" )

Best Practices

  • Always set explicit scheduler name and timezone.
  • Use descriptive task names and logical groups.
  • Keep task bodies small; move heavy fan-out work to async executors.
  • Prefer onFailure hooks and centralized logging for observability.
  • Avoid overly fine-grained intervals without throughput checks.
  • Use spacedDelay() or no-overlap patterns when tasks can run longer than their interval.
  • Use .onOneServer() for cluster-wide jobs that must run once (v1.18+).
  • Inspect scheduling from the CLI with boxlang schedule and boxlang schedule --json (v1.18+).

Troubleshooting

  • Scheduler not found: confirm schedulerStart() was called and schedulerList() contains the name.
  • Task never firing: verify timezone, cron expression, and start/end constraints.
  • Duplicate/stacked runs: use spacing/no-overlap strategies instead of strict fixed-rate behavior.
  • Task runs on every node: add .onOneServer() and use a shared cacheName (v1.18+).
  • Silent failures: inspect scheduler logger output and schedulerStats() counters (totalFailures, lastResult).

Comments

Markdown supported