> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# LuaJob

> Pre-defined scheduled tasks that run automatically

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

```typescript theme={null}
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
    }]);
  }
});
```

<Warning>
  **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.
</Warning>

<Note>
  Pre-defined jobs that are part of your agent configuration. Use with LuaAgent.
</Note>

## When to Use

<CardGroup cols={2}>
  <Card title="LuaJob (Pre-defined)" icon="calendar">
    For scheduled tasks defined at agent setup

    * Daily reports
    * Weekly summaries
    * Cleanup tasks
    * Monitoring jobs

    **User access:** `User.get(userId)` from metadata
  </Card>

  <Card title="Jobs API (Dynamic)" icon="plus">
    For tasks created on-demand from tools

    * User reminders
    * Follow-ups
    * One-time notifications
    * Context-specific tasks

    **User access:** `jobInstance.user()` (automatic!)
  </Card>
</CardGroup>

<Warning>
  **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()`.
</Warning>

## Comparison: LuaJob vs Jobs API

Understanding user access in different job types:

| Feature              | LuaJob (Pre-defined)    | Jobs API (Dynamic)   |
| -------------------- | ----------------------- | -------------------- |
| **When Defined**     | At agent setup          | Runtime, from tools  |
| **User Context**     | ❌ None                  | ✅ Automatic          |
| **Get User**         | `User.get(userId)`      | `jobInstance.user()` |
| **userId Required?** | ✅ Yes (from metadata)   | ❌ No (automatic)     |
| **Use Case**         | Regular scheduled tasks | User-triggered tasks |

**Example - Pre-defined LuaJob:**

```typescript theme={null}
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):**

```typescript theme={null}
await Jobs.create({
  execute: async (jobInstance) => {
    const user = await jobInstance.user();  // ← Automatic!
  }
});
```

## Constructor

### new LuaJob(config)

Creates a new pre-defined job.

<ParamField path="config" type="LuaJobConfig" required>
  Job configuration object
</ParamField>

## Configuration Parameters

### Required Fields

<ParamField path="name" type="string" required>
  Unique job name

  **Format**: lowercase, hyphens, underscores

  **Examples**: `'daily-report'`, `'weekly-cleanup'`
</ParamField>

<ParamField path="schedule" type="JobSchedule" required>
  When and how often the job runs
</ParamField>

<ParamField path="execute" type="function" required>
  Function that runs when the job triggers

  **Signature:** `(job: JobInstance) => Promise<any>`
</ParamField>

### Optional Fields

<ParamField path="description" type="string">
  Job description for documentation
</ParamField>

<ParamField path="metadata" type="object">
  Static metadata available to execute function
</ParamField>

<ParamField path="timeout" type="number">
  Maximum execution time in seconds

  **Default:** 300 (5 minutes)
</ParamField>

<ParamField path="retry" type="object">
  Retry configuration

  ```typescript theme={null}
  retry: {
    maxAttempts: number;
    backoffSeconds?: number;
  }
  ```
</ParamField>

<ParamField path="activate" type="boolean">
  Whether job is active

  **Default:** true
</ParamField>

## Schedule Types

### Interval (Fixed intervals)

```typescript theme={null}
schedule: {
  type: 'interval',
  seconds: number
}
```

**Examples:**

```typescript theme={null}
// 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)

```typescript theme={null}
schedule: {
  type: 'cron',
  expression: string  // Standard cron expression
  timezone?: string   // Optional timezone (e.g., 'America/New_York')
}
```

**Examples:**

```typescript theme={null}
// 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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
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

| Property        | Type         | Description                                     |
| --------------- | ------------ | ----------------------------------------------- |
| `id`            | `string`     | Unique job identifier                           |
| `name`          | `string`     | Job name                                        |
| `activeVersion` | `JobVersion` | The active version with schedule, timeout, etc. |
| `metadata`      | `object`     | Job metadata                                    |
| `data`          | `Job`        | Full job data including all versions            |

### jobInstance.user()

Get the user associated with the job.

**Returns:** `Promise<UserDataInstance>`

```typescript theme={null}
const user = await jobInstance.user();
await user.send([{ type: 'text', text: 'Message' }]);
```

### jobInstance.metadata

Access job metadata.

```typescript theme={null}
const threshold = jobInstance.metadata.alertThreshold;
```

### jobInstance.updateMetadata(data)

Update job metadata dynamically.

```typescript theme={null}
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>`

```typescript theme={null}
// 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).

```typescript theme={null}
// Self-terminate after condition met
if (conditionMet) {
  await jobInstance.delete();
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Choose Appropriate Schedule">
    Use cron for specific times, interval for regular checks

    ```typescript theme={null}
    // ✅ 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
    ```
  </Accordion>

  <Accordion title="Handle Errors Gracefully">
    Jobs should not throw - return error status instead

    ```typescript theme={null}
    execute: async (job) => {
      try {
        await doWork();
        return { success: true };
      } catch (error) {
        console.error('Job failed:', error);
        return { success: false, error: error.message };
      }
    }
    ```
  </Accordion>

  <Accordion title="Use Retry for Critical Jobs">
    Configure retries for important operations

    ```typescript theme={null}
    retry: {
      maxAttempts: 3,
      backoffSeconds: 60  // Retry after 1 minute
    }
    ```
  </Accordion>

  <Accordion title="Keep Jobs Fast">
    Set appropriate timeouts and avoid long-running operations

    ```typescript theme={null}
    timeout: 120,  // 2 minutes max (in seconds)
    ```
  </Accordion>
</AccordionGroup>

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

## Invoking an Agent from a Pre-defined Job

Use `Agents.invoke` to delegate work to a conversational agent from inside a scheduled job. Pre-defined jobs have no ambient user, so pass a `userId` when you have one (e.g. from job metadata). When no `userId` is available, omit it and the invocation runs without user identity — no conversation history is stored.

```typescript theme={null}
import { LuaJob, Agents, User } from 'lua-cli';

// With a known user — invocation runs in their context
const userReminderJob = new LuaJob({
  name: 'weekly-summary',
  schedule: { type: 'cron', expression: '0 9 * * 1' },
  metadata: { userId: 'user_abc123' },

  execute: async (job) => {
    const result = await Agents.invoke('summary-agent', {
      prompt: 'Generate the weekly activity summary for this user.',
      userId: job.metadata.userId, // conversation history stored for this user
    });

    const user = await User.get(job.metadata.userId);
    await user.send([{ type: 'text', text: result.text }]);
    return { sent: true };
  },
});

// Without a user — no conversation history stored
const dailyDigestJob = new LuaJob({
  name: 'daily-digest-generator',
  schedule: { type: 'cron', expression: '0 6 * * *' },

  execute: async (job) => {
    const result = await Agents.invoke('digest-agent', {
      prompt: 'Generate the daily digest.',
      // userId omitted — invocation runs without user identity
    });
    return { digest: result.text };
  },
});
```

<Card title="Agents API Reference" href="/api/agents" icon="robot">
  Full documentation for Agents.invoke — options, output shape, error handling, and more examples
</Card>

## Comparison: LuaJob vs Jobs API

| Feature           | LuaJob (Pre-defined)    | Jobs API (Dynamic)        |
| ----------------- | ----------------------- | ------------------------- |
| **When Defined**  | At agent setup          | Runtime, from tools       |
| **Use Case**      | Regular scheduled tasks | On-demand, user-triggered |
| **Schedule**      | Interval or Cron        | Once, Interval, or Cron   |
| **Examples**      | Daily reports, cleanup  | Reminders, follow-ups     |
| **Configuration** | Static in code          | Dynamic with tool input   |

## Related APIs

<CardGroup cols={2}>
  <Card title="Jobs API" href="/api/jobs" icon="plus">
    Dynamic job creation
  </Card>

  <Card title="Agents API" href="/api/agents" icon="robot">
    Invoke another agent from a job
  </Card>

  <Card title="LuaAgent" href="/api/luaagent" icon="robot">
    Agent configuration
  </Card>

  <Card title="User API" href="/api/user" icon="user">
    Send messages
  </Card>

  <Card title="Data API" href="/api/data" icon="database">
    Store and retrieve data
  </Card>
</CardGroup>

## See Also

* [Jobs API](/api/jobs) - Dynamic job creation
* [Agents API](/api/agents) - Invoking agents from scheduled jobs
* [Workflows Concept](/concepts/workflows)
* [Tool Examples](/examples/overview)
