Skip to main content

Overview

LuaJob allows you to define scheduled tasks that run automatically without user interaction. Unlike the Jobs API which creates jobs dynamically, LuaJob is for pre-defined jobs that are part of your agent configuration.
import { LuaJob, User } from 'lua-cli';

const dailyReport = new LuaJob({
  name: 'daily-sales-report',
  description: 'Generate daily sales summary',
  metadata: {
    userId: 'user_abc123'  // Store user ID to notify
  },
  schedule: {
    type: 'cron',
    expression: '0 9 * * *'  // Every day at 9 AM
  },
  execute: async (job) => {
    // Generate report
    const report = await generateSalesReport();
    
    // ⚠️ Pre-defined jobs have NO conversational context
    // Get user by ID from metadata
    const user = await User.get(job.metadata.userId);
    await user.send([{
      type: 'text',
      text: report
    }]);
  }
});
No Conversational Context: LuaJob executes outside of user conversations. You MUST use User.get(userId) with a userId from metadata. The job does NOT have jobInstance.user() - that’s only for dynamic jobs.
New in v3.0.0: Pre-defined jobs that are part of your agent configuration. Use with LuaAgent.

When to Use

LuaJob (Pre-defined)

For scheduled tasks defined at agent setup
  • Daily reports
  • Weekly summaries
  • Cleanup tasks
  • Monitoring jobs
User access: User.get(userId) from metadata

Jobs API (Dynamic)

For tasks created on-demand from tools
  • User reminders
  • Follow-ups
  • One-time notifications
  • Context-specific tasks
User access: jobInstance.user() (automatic!)
Key Difference: Pre-defined LuaJob has NO user context. Use User.get(userId) with ID from metadata. Dynamic jobs (Jobs API) automatically have user context via jobInstance.user().

Comparison: LuaJob vs Jobs API

Understanding user access in different job types:
FeatureLuaJob (Pre-defined)Jobs API (Dynamic)
When DefinedAt agent setupRuntime, from tools
User Context❌ None✅ Automatic
Get UserUser.get(userId)jobInstance.user()
userId Required?✅ Yes (from metadata)❌ No (automatic)
Use CaseRegular scheduled tasksUser-triggered tasks
Example - Pre-defined LuaJob:
const job = new LuaJob({
  metadata: { userId: 'user_abc123' },  // ← Store userId
  execute: async (job) => {
    const user = await User.get(job.metadata.userId);  // ← Required
  }
});
Example - Dynamic Job (Jobs API):
await Jobs.create({
  execute: async (jobInstance) => {
    const user = await jobInstance.user();  // ← Automatic!
  }
});

Constructor

new LuaJob(config)

Creates a new pre-defined job.
config
LuaJobConfig
required
Job configuration object

Configuration Parameters

Required Fields

name
string
required
Unique job nameFormat: lowercase, hyphens, underscoresExamples: 'daily-report', 'weekly-cleanup'
schedule
JobSchedule
required
When and how often the job runs
execute
function
required
Function that runs when the job triggersSignature: (job: JobInstance) => Promise<any>

Optional Fields

description
string
Job description for documentation
metadata
object
Static metadata available to execute function
timeout
number
Maximum execution time in secondsDefault: 300 (5 minutes)
retry
object
Retry configuration
retry: {
  maxAttempts: number;
  backoffSeconds?: number;
}
activate
boolean
Whether job is activeDefault: true

Schedule Types

Interval (Fixed intervals)

schedule: {
  type: 'interval',
  seconds: number
}
Examples:
// Every 5 minutes
schedule: {
  type: 'interval',
  seconds: 300
}

// Every hour
schedule: {
  type: 'interval',
  seconds: 3600
}

// Every day (86400 seconds)
schedule: {
  type: 'interval',
  seconds: 86400
}

Cron (Cron patterns)

schedule: {
  type: 'cron',
  expression: string  // Standard cron expression
  timezone?: string   // Optional timezone (e.g., 'America/New_York')
}
Examples:
// Every day at 9 AM
schedule: {
  type: 'cron',
  expression: '0 9 * * *'
}

// Every Monday at 8 AM EST
schedule: {
  type: 'cron',
  expression: '0 8 * * 1',
  timezone: 'America/New_York'
}

// Every hour on the hour
schedule: {
  type: 'cron',
  expression: '0 * * * *'
}

// First day of every month at midnight
schedule: {
  type: 'cron',
  expression: '0 0 1 * *'
}

Complete Examples

Daily Sales Report

import { LuaJob, Products, User } from 'lua-cli';

const dailySalesReport = new LuaJob({
  name: 'daily-sales-report',
  description: 'Daily sales summary sent every morning',
  
  metadata: {
    userId: 'user_abc123',  // Store userId to send report to
    reportTime: '09:00',
    timezone: 'America/New_York'
  },
  
  schedule: {
    type: 'cron',
    expression: '0 9 * * *',  // Every day at 9 AM
    timezone: 'America/New_York'
  },
  
  execute: async (jobInstance) => {
    // Fetch sales data
    const products = await Products.get(1, 100);
    const totalValue = products.reduce((sum, p) => sum + (p.price || 0), 0);
    const topProducts = products.sort((a, b) => b.price - a.price).slice(0, 5);
    
    // Build report
    const report = `📊 Daily Sales Report\n\n` +
      `Date: ${new Date().toLocaleDateString()}\n` +
      `Total Products: ${products.length}\n` +
      `Catalog Value: $${totalValue.toFixed(2)}\n\n` +
      `Top 5 Products:\n` +
      topProducts.map(p => `- ${p.name}: $${p.price}`).join('\n');
    
    // ⚠️ Pre-defined jobs have NO user context
    // Get user by ID from metadata
    const user = await User.get(jobInstance.metadata.userId);
    await user.send([{
      type: 'text',
      text: report
    }]);
    
    return {
      success: true,
      productCount: products.length,
      totalValue,
      timestamp: new Date().toISOString()
    };
  }
});

export default dailySalesReport;

Weekly Cleanup Job

import { LuaJob, Data, User } from 'lua-cli';

const weeklyCleanup = new LuaJob({
  name: 'weekly-cleanup',
  description: 'Clean up old data every Sunday',
  
  metadata: {
    adminUserId: 'user_admin123'  // Store admin userId to notify
  },
  
  schedule: {
    type: 'cron',
    expression: '0 0 * * 0'  // Every Sunday at midnight
  },
  
  execute: async (jobInstance) => {
    // Delete old entries (older than 30 days)
    const thirtyDaysAgo = new Date();
    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
    
    const oldEntries = await Data.get('temp-data', 1000);
    let deletedCount = 0;
    
    for (const entry of oldEntries) {
      const createdAt = new Date(entry.createdAt);
      if (createdAt < thirtyDaysAgo) {
        await Data.delete('temp-data', entry.id);
        deletedCount++;
      }
    }
    
    // Send summary to admin
    // ⚠️ Pre-defined jobs have NO user context
    // Get user by ID from metadata
    const user = await User.get(jobInstance.metadata.adminUserId);
    await user.send([{
      type: 'text',
      text: `🧹 Weekly cleanup complete. Deleted ${deletedCount} old entries.`
    }]);
    
    return {
      success: true,
      deletedCount,
      timestamp: new Date().toISOString()
    };
  }
});

export default weeklyCleanup;

Hourly Monitoring

import { LuaJob, env, User } from 'lua-cli';

const systemMonitor = new LuaJob({
  name: 'system-monitor',
  description: 'Monitor system health every hour',
  
  metadata: {
    alertThreshold: 90,
    checkInterval: 'hourly',
    adminUserId: 'user_admin123'  // Store admin userId for alerts
  },
  
  schedule: {
    type: 'interval',
    seconds: 3600  // Every hour
  },
  
  execute: async (jobInstance) => {
    // Check external API health
    try {
      const response = await fetch(`${env('API_URL')}/health`);
      const health = await response.json();
      
      // Alert if threshold exceeded
      if (health.cpuUsage > jobInstance.metadata.alertThreshold) {
        // ⚠️ Pre-defined jobs have NO user context
        // Get user by ID from metadata
        const user = await User.get(jobInstance.metadata.adminUserId);
        await user.send([{
          type: 'text',
          text: `⚠️ Alert: CPU usage is ${health.cpuUsage}%`
        }]);
      }
      
      return {
        success: true,
        cpuUsage: health.cpuUsage,
        memoryUsage: health.memoryUsage,
        timestamp: new Date().toISOString()
      };
    } catch (error) {
      // Alert on failure
      const user = await User.get(jobInstance.metadata.adminUserId);
      await user.send([{
        type: 'text',
        text: `🚨 System monitor failed: ${error.message}`
      }]);
      
      return {
        success: false,
        error: error.message,
        timestamp: new Date().toISOString()
      };
    }
  },
  
  retry: {
    maxAttempts: 3,
    backoffSeconds: 60
  }
});

export default systemMonitor;

Reminders with Metadata

import { LuaJob, Data, User } from 'lua-cli';

const userReminders = new LuaJob({
  name: 'check-reminders',
  description: 'Check and send due reminders every 5 minutes',
  
  schedule: {
    type: 'interval',
    seconds: 300  // Every 5 minutes
  },
  
  execute: async (jobInstance) => {
    // Get reminders due now
    const now = new Date();
    const reminders = await Data.get('reminders', 100);
    let sentCount = 0;
    
    for (const reminder of reminders) {
      const dueTime = new Date(reminder.data.dueAt);
      
      if (dueTime <= now && !reminder.data.sent) {
        // ⚠️ Pre-defined jobs have NO user context
        // Each reminder must store the userId to send to
        const user = await User.get(reminder.data.userId);
        await user.send([{
          type: 'text',
          text: `⏰ Reminder: ${reminder.data.message}`
        }]);
        
        // Mark as sent
        await Data.update('reminders', reminder.id, {
          ...reminder.data,
          sent: true,
          sentAt: now.toISOString()
        });
        
        sentCount++;
      }
    }
    
    return {
      success: true,
      checked: reminders.length,
      sent: sentCount,
      timestamp: now.toISOString()
    };
  }
});

export default userReminders;

Using with LuaAgent

Jobs are added to your agent configuration:
import { LuaAgent } from 'lua-cli';
import dailySalesReport from './jobs/daily-sales-report';
import weeklyCleanup from './jobs/weekly-cleanup';
import systemMonitor from './jobs/system-monitor';

export const agent = new LuaAgent({
  name: 'my-agent',
  persona: '...',
  skills: [...],
  
  jobs: [
    dailySalesReport,
    weeklyCleanup,
    systemMonitor
  ]
});

JobInstance API

The execute function receives a JobInstance with these properties and methods:

Properties

PropertyTypeDescription
idstringUnique job identifier
namestringJob name
activeVersionJobVersionThe active version with schedule, timeout, etc.
metadataobjectJob metadata
dataJobFull job data including all versions

jobInstance.user()

Get the user associated with the job. Returns: Promise<UserDataInstance>
const user = await jobInstance.user();
await user.send([{ type: 'text', text: 'Message' }]);

jobInstance.metadata

Access job metadata.
const threshold = jobInstance.metadata.alertThreshold;

jobInstance.updateMetadata(data)

Update job metadata dynamically.
await jobInstance.updateMetadata({
  lastRun: new Date().toISOString(),
  runCount: (jobInstance.metadata.runCount || 0) + 1
});

jobInstance.trigger(versionId?)

Manually triggers the job execution (ignores schedule). Uses the active version by default. Parameters:
  • versionId (optional): Specific version to execute. Defaults to activeVersion.
Returns: Promise<JobExecution>
// Trigger with default active version
const execution = await jobInstance.trigger();
console.log('Execution ID:', execution.id);

// Trigger with specific version
const execution = await jobInstance.trigger('version_abc123');

jobInstance.delete()

Delete the job (or deactivates if it has versions).
// Self-terminate after condition met
if (conditionMet) {
  await jobInstance.delete();
}

Best Practices

Use cron for specific times, interval for regular checks
// ✅ Cron for specific times
schedule: { type: 'cron', expression: '0 9 * * *' }  // 9 AM daily

// ✅ Interval for regular polling
schedule: { type: 'interval', seconds: 300 }  // Every 5 min
Jobs should not throw - return error status instead
execute: async (job) => {
  try {
    await doWork();
    return { success: true };
  } catch (error) {
    console.error('Job failed:', error);
    return { success: false, error: error.message };
  }
}
Configure retries for important operations
retry: {
  maxAttempts: 3,
  backoffSeconds: 60  // Retry after 1 minute
}
Set appropriate timeouts and avoid long-running operations
timeout: 120,  // 2 minutes max (in seconds)

Cron Pattern Reference

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (0 = Sunday)
│ │ │ │ │
* * * * *
Common Patterns:
  • 0 * * * * - Every hour on the hour
  • 0 9 * * * - Every day at 9 AM
  • 0 9 * * 1 - Every Monday at 9 AM
  • 0 0 1 * * - First day of every month at midnight
  • */15 * * * * - Every 15 minutes
  • 0 9-17 * * 1-5 - 9 AM to 5 PM, Monday to Friday

Comparison: LuaJob vs Jobs API

FeatureLuaJob (Pre-defined)Jobs API (Dynamic)
When DefinedAt agent setupRuntime, from tools
Use CaseRegular scheduled tasksOn-demand, user-triggered
ScheduleInterval or CronOnce, Interval, or Cron
ExamplesDaily reports, cleanupReminders, follow-ups
ConfigurationStatic in codeDynamic with tool input

See Also