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

# Jobs API

> Dynamically create scheduled tasks from within your tools

## Overview

The Jobs API allows you to dynamically create scheduled tasks from within your tools. Use this to defer work, schedule reminders, or automate recurring tasks.

```typescript theme={null}
import { Jobs } from 'lua-cli';

// Create a one-time reminder
const job = await Jobs.create({
  name: 'user-reminder',
  metadata: { message: 'Team meeting in 10 minutes' },
  schedule: {
    type: 'once',
    executeAt: new Date(Date.now() + 600000)
  },
  execute: async (jobInstance) => {
    // ✅ Dynamic jobs automatically have user context!
    const user = await jobInstance.user();  // No userId needed
    await user.send([{
      type: 'text',
      text: jobInstance.metadata.message
    }]);
  }
});
```

<Note>
  **Automatic User Context:** Dynamic jobs created from tools automatically know which user triggered them. Use `jobInstance.user()` to get the user - no userId required! This is different from pre-defined LuaJob which requires `User.get(userId)`.
</Note>

<Note>
  Dynamically create jobs at runtime from within your tools. Pairs with the LuaJob class for pre-defined jobs.
</Note>

## Import

```typescript theme={null}
import { Jobs } from 'lua-cli';
// or
import { Jobs } from 'lua-cli/skill';
```

## Capabilities

<CardGroup cols={2}>
  <Card title="Dynamic Creation" icon="plus">
    Create jobs on-demand from tools
  </Card>

  <Card title="One-time Tasks" icon="calendar-day">
    Schedule tasks for specific times
  </Card>

  <Card title="Recurring Jobs" icon="rotate">
    Set up intervals or cron patterns
  </Card>

  <Card title="Automatic User Context" icon="user">
    Jobs automatically know which user triggered them - use `jobInstance.user()`
  </Card>
</CardGroup>

## Jobs API vs LuaJob

Understanding user access in different job types:

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

<Note>
  **Why the difference?** Dynamic jobs are created during a user conversation, so they automatically capture that user's context. Pre-defined jobs run on a schedule with no specific user, so you must explicitly provide a userId if you want to notify someone.
</Note>

## Methods

### Jobs.create(config)

Creates a new scheduled job.

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

**Returns:** `Promise<JobInstance>`

**Example:**

```typescript theme={null}
const job = await Jobs.create({
  name: 'reminder-task',
  description: 'Remind user about meeting',
  metadata: { message: 'Don\'t forget the meeting!' },
  schedule: {
    type: 'once',
    executeAt: new Date(Date.now() + 3600000)
  },
  execute: async (jobInstance) => {
    const user = await jobInstance.user();
    await user.send([{
      type: 'text',
      text: jobInstance.metadata.message
    }]);
    return { success: true };
  }
});
```

### Jobs.getJob(jobId)

Retrieves a job by ID.

<ParamField path="jobId" type="string" required>
  Job ID to retrieve
</ParamField>

**Returns:** `Promise<JobInstance>`

**Example:**

```typescript theme={null}
const job = await Jobs.getJob('job_123');
console.log(job.name);
console.log(job.activeVersion?.schedule);
```

### Jobs.getAll(options?)

Retrieves all jobs for the current agent.

<ParamField path="options.includeDynamic" type="boolean">
  Include dynamically created jobs (default: false)
</ParamField>

**Returns:** `Promise<JobInstance[]>`

**Example:**

```typescript theme={null}
// Get all jobs including dynamically created ones
const jobs = await Jobs.getAll({ includeDynamic: true });

for (const job of jobs) {
  console.log(`${job.name}: ${job.data.active ? 'active' : 'inactive'}`);
}

// Find a specific job by name
const trackingJob = jobs.find(j => j.name.startsWith('track-game-'));
if (trackingJob) {
  await trackingJob.deactivate();
}
```

## Job Configuration

### Required Fields

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

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

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

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

### Optional Fields

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

<ParamField path="metadata" type="object">
  Data to pass to execute function

  **Important:** Use metadata to pass data - the execute function cannot access parent scope!
</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;     // Max retry attempts
    backoffSeconds?: number; // Seconds between retries (optional)
  }
  ```
</ParamField>

<ParamField path="activate" type="boolean">
  Whether to activate immediately

  **Default:** true
</ParamField>

## Schedule Types

### Once (One-time execution)

```typescript theme={null}
schedule: {
  type: 'once',
  executeAt: Date  // When to run
}
```

**Examples:**

```typescript theme={null}
// Run in 1 hour
schedule: {
  type: 'once',
  executeAt: new Date(Date.now() + 3600000)
}

// Run at specific time
schedule: {
  type: 'once',
  executeAt: new Date('2025-12-25T09:00:00Z')
}
```

### Interval (Recurring at fixed intervals)

```typescript theme={null}
schedule: {
  type: 'interval',
  seconds: number  // Seconds between executions
}
```

**Examples:**

```typescript theme={null}
// Run every 5 minutes
schedule: {
  type: 'interval',
  seconds: 300
}

// Run every hour
schedule: {
  type: 'interval',
  seconds: 3600
}
```

### Cron (Schedule with cron pattern)

```typescript theme={null}
schedule: {
  type: 'cron',
  expression: string  // 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 15 minutes
schedule: {
  type: 'cron',
  expression: '*/15 * * * *'
}
```

## Complete Examples

### Reminder Tool

```typescript theme={null}
import { LuaTool, Jobs, JobInstance } from 'lua-cli/skill';
import { z } from 'zod';

export default class ReminderTool implements LuaTool {
  name = 'set_reminder';
  description = 'Set a reminder to notify user later';
  
  inputSchema = z.object({
    message: z.string().describe('Reminder message'),
    minutes: z.number().min(1).max(10080).describe('Minutes from now')
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    // Create job to notify user later
    const job = await Jobs.create({
      name: `reminder-${Date.now()}`,
      description: 'User reminder',
      
      // ✅ Pass data via metadata (not parent scope!)
      metadata: {
        message: input.message,
        setAt: new Date().toISOString()
      },
      
      schedule: {
        type: 'once',
        executeAt: new Date(Date.now() + input.minutes * 60000)
      },
      
      execute: async (jobInstance: JobInstance) => {
        const user = await jobInstance.user();
        const message = jobInstance.metadata.message;
        
        await user.send([{
          type: 'text',
          text: `⏰ Reminder: ${message}`
        }]);
        
        return {
          success: true,
          deliveredAt: new Date().toISOString()
        };
      }
    });
    
    return {
      success: true,
      message: `Reminder set for ${input.minutes} minutes from now`,
      jobId: job.id,
      executeAt: job.activeVersion?.schedule?.executeAt
    };
  }
}
```

### Follow-up Tool

```typescript theme={null}
import { LuaTool, Jobs, JobInstance, Data } from 'lua-cli/skill';
import { z } from 'zod';

export default class ScheduleFollowupTool implements LuaTool {
  name = 'schedule_followup';
  description = 'Schedule a follow-up message for customer support';
  
  inputSchema = z.object({
    ticketId: z.string(),
    followupHours: z.number().min(1).max(168),
    message: z.string()
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    // Create job for follow-up
    const job = await Jobs.create({
      name: `followup-${input.ticketId}`,
      description: `Follow-up for ticket ${input.ticketId}`,
      
      metadata: {
        ticketId: input.ticketId,
        message: input.message
      },
      
      schedule: {
        type: 'once',
        executeAt: new Date(Date.now() + input.followupHours * 3600000)
      },
      
      execute: async (jobInstance: JobInstance) => {
        const user = await jobInstance.user();
        const { ticketId, message } = jobInstance.metadata;
        
        // Check if ticket is still open
        const tickets = await Data.search('tickets', ticketId, 1);
        
        if (tickets.length > 0 && tickets[0].status === 'open') {
          // Send follow-up
          await user.send([{
            type: 'text',
            text: `📋 Ticket #${ticketId} Follow-up:\n\n${message}`
          }]);
          
          // Update ticket
          await Data.update('tickets', tickets[0].id, {
            ...tickets[0].data,
            followupSent: true,
            followupAt: new Date().toISOString()
          });
          
          return { success: true, sent: true };
        }
        
        return { success: true, sent: false, reason: 'Ticket closed' };
      }
    });
    
    return {
      success: true,
      message: `Follow-up scheduled for ${input.followupHours} hours`,
      jobId: job.id
    };
  }
}
```

### Recurring Report

```typescript theme={null}
import { LuaTool, Jobs, JobInstance, Products } from 'lua-cli/skill';
import { z } from 'zod';

export default class DailyReportTool implements LuaTool {
  name = 'setup_daily_report';
  description = 'Set up daily sales report';
  
  inputSchema = z.object({
    timeOfDay: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
      .describe('Time in HH:MM format (24-hour)')
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    const [hour, minute] = input.timeOfDay.split(':');
    
    const job = await Jobs.create({
      name: 'daily-sales-report',
      description: 'Daily sales summary',
      
      metadata: {
        reportTime: input.timeOfDay
      },
      
      schedule: {
        type: 'cron',
        expression: `${minute} ${hour} * * *`  // Every day at specified time
      },
      
      execute: async (jobInstance: JobInstance) => {
        // Get products sold today
        const products = await Products.get(1, 100);
        const totalValue = products.reduce((sum, p) => sum + (p.price || 0), 0);
        
        const report = `📊 Daily Sales Report\n\n` +
          `Total Products: ${products.length}\n` +
          `Catalog Value: $${totalValue.toFixed(2)}\n` +
          `Report Time: ${jobInstance.metadata.reportTime}`;
        
        const user = await jobInstance.user();
        await user.send([{ type: 'text', text: report }]);
        
        return {
          success: true,
          productCount: products.length,
          totalValue
        };
      }
    });
    
    return {
      success: true,
      message: `Daily report scheduled for ${input.timeOfDay}`,
      jobId: job.id
    };
  }
}
```

## Important: Metadata Pattern

<Warning>
  **Jobs execute functions must be self-contained!** They cannot access parent scope variables.
</Warning>

```typescript theme={null}
// ❌ WRONG - Accessing parent scope
async execute(input: any) {
  const userMessage = input.message;  // This variable...
  
  await Jobs.create({
    execute: async (job) => {
      // ...is NOT available here! ❌
      await user.send(userMessage);  // Error: userMessage is not defined
    }
  });
}

// ✅ CORRECT - Using metadata
async execute(input: any) {
  await Jobs.create({
    metadata: {
      message: input.message  // ✅ Pass via metadata
    },
    execute: async (job) => {
      const message = job.metadata.message;  // ✅ Access from metadata
      const user = await job.user();
      await user.send([{ type: 'text', text: message }]);
    }
  });
}
```

**Why?** Jobs are serialized, bundled, and executed in an isolated sandbox. They can't access the parent function's scope.

## JobInstance Methods

The `JobInstance` passed to execute functions provides:

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

Gets the user who triggered the job.

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

<Note>
  **Automatic User Context:** This method is ONLY available for dynamic jobs created via `Jobs.create()`. The user context is automatically captured when the job is created from a tool. Pre-defined LuaJob must use `User.get(userId)` instead.
</Note>

**Example:**

```typescript theme={null}
// In a tool creating a dynamic job
await Jobs.create({
  execute: async (jobInstance) => {
    // ✅ Works! User context automatically available
    const user = await jobInstance.user();
    await user.send([{ type: 'text', text: 'Job complete!' }]);
  }
});
```

**Comparison with LuaJob:**

```typescript theme={null}
// ❌ This won't work in pre-defined LuaJob
const job = new LuaJob({
  execute: async (job) => {
    const user = await job.user();  // ❌ No user() method!
  }
});

// ✅ Use this for LuaJob instead
const job = new LuaJob({
  metadata: { userId: 'user_abc123' },
  execute: async (job) => {
    const user = await User.get(job.metadata.userId);  // ✅ Works!
  }
});
```

### job.metadata

Access to the metadata passed during creation.

**Example:**

```typescript theme={null}
execute: async (job) => {
  console.log(job.metadata.customData);
}
```

### job.updateMetadata(data)

Updates job metadata.

**Example:**

```typescript theme={null}
execute: async (job) => {
  await job.updateMetadata({
    lastRun: new Date().toISOString(),
    runCount: (job.metadata.runCount || 0) + 1
  });
}
```

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

**Example:**

```typescript theme={null}
// Trigger with default active version
const execution = await job.trigger();
console.log('Execution ID:', execution.id);
console.log('Status:', execution.status);

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

### job.delete()

Deletes the job (or deactivates if it has versions).

**Example:**

```typescript theme={null}
execute: async (job) => {
  // Do work...
  
  // Delete one-time job after execution
  await job.delete();
}
```

### job.activate()

Activates the job, enabling it to run on schedule.

**Returns:** `Promise<JobInstance>`

**Example:**

```typescript theme={null}
// Re-enable a paused job
const job = await Jobs.getJob('job_123');
await job.activate();
console.log('Job is now active');
```

### job.deactivate()

Deactivates the job, preventing it from running on schedule. Useful for jobs that should stop themselves.

**Returns:** `Promise<JobInstance>`

**Example:**

```typescript theme={null}
// Job stops itself when work is complete
execute: async (job) => {
  const data = await fetchData();
  
  if (data.isComplete) {
    // Stop the recurring job
    await job.deactivate();
    return { action: 'completed', stopped: true };
  }
  
  return { action: 'processed', data };
}
```

## Retry Configuration

```typescript theme={null}
await Jobs.create({
  name: 'important-task',
  
  retry: {
    maxAttempts: 3,
    backoffSeconds: 60  // Wait 60s between retries
  },
  
  execute: async (job) => {
    // If this throws, will retry up to 3 times
    await criticalOperation();
  }
});
```

## Timeout Configuration

```typescript theme={null}
await Jobs.create({
  name: 'long-running-task',
  timeout: 300,  // 5 minutes max (in seconds)
  
  execute: async (job) => {
    // Will be terminated if exceeds 5 minutes
    await longOperation();
  }
});
```

## Best Practices

<AccordionGroup>
  <Accordion title="✅ Use Unique Names">
    Give each job a descriptive, unique name

    ```typescript theme={null}
    name: `reminder-${Date.now()}`
    name: `followup-ticket-${ticketId}`
    ```
  </Accordion>

  <Accordion title="✅ Pass Data via Metadata">
    Always use metadata to pass data to execute function

    ```typescript theme={null}
    metadata: {
      userId: input.userId,
      message: input.message,
      timestamp: new Date().toISOString()
    }
    ```
  </Accordion>

  <Accordion title="✅ Handle Errors Gracefully">
    Implement error handling in execute function

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

  <Accordion title="❌ Don't Access Parent Scope">
    Execute functions are isolated - use metadata!

    ```typescript theme={null}
    // ❌ This won't work
    const data = input.value;
    execute: async (job) => {
      console.log(data); // Error!
    }

    // ✅ Use metadata instead
    metadata: { data: input.value },
    execute: async (job) => {
      console.log(job.metadata.data); // Works!
    }
    ```
  </Accordion>
</AccordionGroup>

<Note title="Debugging tip">
  If a job isn't behaving as expected, add `console.log` statements and check execution logs:

  ```typescript theme={null}
  execute: async (jobInstance) => {
    const data = await Data.get('orders', { status: 'pending' });
    console.log(`[Job] Found ${data.data.length} pending orders`);
    // ... rest of job
  }
  ```

  Then check: `lua logs --type job --name my-job-name --limit 10`. See the [Debugging Skills guide](/cli/debugging) for the full workflow.
</Note>

## Related APIs

<CardGroup cols={2}>
  <Card title="LuaJob" href="/api/luajob" icon="clock">
    Pre-defined scheduled jobs
  </Card>

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

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

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

  <Card title="Debugging Skills" href="/cli/debugging" icon="bug">
    Inspect runtime return values
  </Card>
</CardGroup>

## See Also

* [LuaJob Class](/api/luajob) - Pre-defined jobs
* [Tool Examples](/examples/overview)
* [Concepts: Workflows](/concepts/workflows)
