# Jobs API

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.

## Import

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

## Overview

The Jobs API enables:
- Dynamic job creation from tools
- One-time scheduled tasks
- Recurring jobs with cron patterns
- Passing data via metadata
- User-scoped job execution

## Methods

### `Jobs.create(config)`

Creates a new scheduled job.

**Parameters:**
- `config` (object) - Job configuration

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

**Example:**
```typescript
const job = await Jobs.create({
  name: 'user-reminder',
  description: 'Remind user about meeting',
  metadata: { message: 'Team meeting in 10 minutes' },
  schedule: {
    type: 'once',
    executeAt: new Date(Date.now() + 600000)
  },
  execute: async (jobInstance) => {
    const user = await jobInstance.user();
    await user.send([{
      type: 'text',
      text: jobInstance.metadata.message
    }]);
    return { success: true };
  }
});

console.log('Job created:', job.id);
```

### `Jobs.get(jobId)`

Retrieves a job by ID.

**Parameters:**
- `jobId` (string) - Job ID

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

**Example:**
```typescript
const job = await Jobs.get('job_123');
console.log(job.name);
console.log(job.schedule);
```

## Job Configuration

```typescript
interface JobConfig {
  /** Unique job name */
  name: string;
  
  /** Job description */
  description?: string;
  
  /** Data to pass to execute function */
  metadata?: Record<string, any>;
  
  /** When/how often to run */
  schedule: JobSchedule;
  
  /** Maximum execution time in ms */
  timeout?: number;
  
  /** Retry configuration */
  retry?: {
    maxRetries: number;
    backoffSeconds: number;
  };
  
  /** Whether to activate immediately */
  activate?: boolean;
  
  /** Execute function */
  execute: (job: JobInstance) => Promise<any>;
}
```

## Schedule Types

### Once (One-time execution)

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

**Example:**
```typescript
// 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
schedule: {
  type: 'interval',
  intervalSeconds: number  // Seconds between executions
}
```

**Example:**
```typescript
// Run every 5 minutes
schedule: {
  type: 'interval',
  intervalSeconds: 300
}

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

### Cron (Schedule with cron pattern)

```typescript
schedule: {
  type: 'cron',
  pattern: string  // Cron pattern
}
```

**Example:**
```typescript
// Every day at 9 AM
schedule: {
  type: 'cron',
  pattern: '0 9 * * *'
}

// Every Monday at 8 AM
schedule: {
  type: 'cron',
  pattern: '0 8 * * 1'
}

// Every 15 minutes
schedule: {
  type: 'cron',
  pattern: '*/15 * * * *'
}
```

## Complete Examples

### Reminder Tool

```typescript
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.schedule.executeAt
    };
  }
}
```

### Follow-up Tool

```typescript
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.data.length > 0 && tickets.data[0].data.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.data[0].id, {
            ...tickets.data[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
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',
        pattern: `${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

**⚠️ Jobs execute functions must be self-contained!**

```typescript
// ❌ 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

The `JobInstance` passed to execute functions provides:

### `job.user()`

Gets the user who triggered the job.

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

**Example:**
```typescript
execute: async (job) => {
  const user = await job.user();
  await user.send([{ type: 'text', text: 'Job complete!' }]);
}
```

### `job.metadata`

Access to the metadata passed during creation.

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

### `job.updateMetadata(data)`

Updates job metadata.

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

### `job.delete()`

Deletes the job.

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

## Retry Configuration

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

## Timeout Configuration

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

## Best Practices

### ✅ DO
- Use unique, descriptive job names
- Pass all necessary data via metadata
- Handle errors gracefully in execute function
- Set appropriate timeouts
- Use retry for critical jobs

### ❌ DON'T
- Don't access parent scope variables
- Don't create jobs inside jobs (use metadata to track state)
- Don't use extremely short intervals (< 1 minute)
- Don't forget to handle errors

## Related APIs

- [LuaJob](./LuaJob.md) - Pre-defined scheduled jobs
- [JobInstance](./instances/JobInstance.md) - Job instance methods
- [User API](./User.md) - Send messages from jobs

## See Also

- [Dynamic Job Creation Guide](../../DYNAMIC_JOB_CREATION_API.md)
- [Job Examples](../../template/src/jobs/)
- [CreateInlineJob Example](../../template/src/skills/tools/CreateInlineJob.ts)

