# Implementing Scheduled Tasks in AionUi: A Complete Guide to the Cron Subsystem

> Learn to implement scheduled tasks in AionUi with our guide to the cron subsystem. Explore SQLite persistence, timer management, and React frontend integration for efficient job scheduling.

- Repository: [OfficeAI/AionUi](https://github.com/iofficeai/aionui)
- Tags: how-to-guide
- Published: 2026-02-15

---

**AionUi implements scheduled tasks through a three-layer cron subsystem comprising SQLite persistence in [`CronStore.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronStore.ts), timer management and execution logic in [`CronService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronService.ts), and React frontend hooks that synchronize job state via IPC.**

AionUi ships with a full-featured cron subsystem that enables automatic message scheduling within conversations. Whether you need daily briefings, hourly updates, or one-time reminders, the implementation leverages native timers, SQLite persistence, and Electron's power-save blockers to ensure reliable execution. This guide examines the architecture, key source files, and practical implementation patterns for building scheduled tasks in AionUi.

## Architecture Overview

The scheduled task implementation splits responsibilities across four logical layers:

| Layer | Responsibility | Key Source Files |
|-------|---------------|------------------|
| **Persistence** | SQLite storage and CRUD operations for job definitions | [`src/process/services/cron/CronStore.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronStore.ts) |
| **Engine** | Timer creation, job execution, retry logic, and system resume handling | [`src/process/services/cron/CronService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronService.ts) |
| **Busy-Guard** | Prevents concurrent execution when conversations are processing messages | [`src/process/services/cron/CronBusyGuard.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronBusyGuard.ts) |
| **Frontend** | React components and hooks for job management via IPC | [`src/renderer/pages/cron/components/CronJobManager.tsx`](https://github.com/iOfficeAI/AionUi/blob/main/src/renderer/pages/cron/components/CronJobManager.tsx), [`useCronJobs.ts`](https://github.com/iOfficeAI/AionUi/blob/main/useCronJobs.ts) |

## Persistence Layer: CronStore

The `CronStore` class in [`src/process/services/cron/CronStore.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronStore.ts) handles all database interactions for scheduled tasks. It defines the `CronJob` data model (lines 18-43) and provides bidirectional serialization through `jobToRow` and `rowToJob` functions.

**Key capabilities include:**

- **CRUD Operations**: `insert()`, `update()`, `delete()`, `listAll()`, `listByConversation()`, `listEnabled()`, and `deleteByConversation()`
- **Singleton Pattern**: Exported as `cronStore` singleton for application-wide access
- **Schema**: Jobs store schedule expressions, next run timestamps, retry counts, and enabled status in the `cron_jobs` table

## Scheduling Engine: CronService

[`CronService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronService.ts) in [`src/process/services/cron/CronService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronService.ts) implements the core scheduling logic, managing native timers and job lifecycle.

### Initialization and Lifecycle

The `init()` method (lines 45-63) loads all enabled jobs from `cronStore.listEnabled()` and activates their timers. It also registers system resume listeners to handle missed executions after sleep.

### Job Creation and Timer Management

The `addJob()` method (lines 70-126) persists new jobs and creates appropriate timers based on `schedule.kind`:

- **`cron`**: Uses `Cron` object for standard cron expressions
- **`every`**: Uses `setInterval` for recurring intervals
- **`at`**: Uses `setTimeout` for one-time scheduled execution

Each timer updates `nextRunAtMs` in the database and emits `ipcBridge.cron.onJobUpdated` to synchronize the frontend.

### Execution and Retry Logic

The `executeJob()` method (lines 90-140) checks `cronBusyGuard.isProcessing()` before firing. If the conversation is busy, it increments `retryCount` and schedules a 30-second retry. Successful executions update the conversation's `modifyTime` to trigger UI reordering.

### System Resume and Power Management

`handleSystemResume()` (lines 55-92) detects missed jobs after system sleep, inserts warning messages via `insertMissedJobMessage`, and restarts timers. `updatePowerBlocker()` (lines 32-51) uses Electron's `powerSaveBlocker.start('prevent-app-suspension')` to keep the app alive while jobs are active.

## Concurrency Control: CronBusyGuard

[`CronBusyGuard.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronBusyGuard.ts) in [`src/process/services/cron/CronBusyGuard.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronBusyGuard.ts) prevents race conditions by tracking per-conversation processing flags. The `isProcessing()` method (line 25) is consulted before each job execution. If a conversation is busy, the guard triggers retry logic with `waitForIdle()` and provides `cleanup()` utilities for housekeeping.

## Frontend Integration

The renderer process interacts with the cron subsystem through React hooks and IPC bridges.

### useCronJobs Hook

Located in [`src/renderer/pages/cron/hooks/useCronJobs.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/renderer/pages/cron/hooks/useCronJobs.ts), this hook (lines 92-163) fetches jobs via `ipcBridge.cron.listJobsByConversation` and subscribes to real-time updates through `onJobCreated`, `onJobUpdated`, and `onJobRemoved` events. It exposes `pauseJob`, `resumeJob`, `deleteJob`, and `updateJob` mutations that sync with the backend.

### UI Components

- **CronJobManager** ([`src/renderer/pages/cron/components/CronJobManager.tsx`](https://github.com/iOfficeAI/AionUi/blob/main/src/renderer/pages/cron/components/CronJobManager.tsx)): Header button displaying job status with color-coded indicators and opening the editing drawer
- **CronJobDrawer** ([`src/renderer/pages/cron/components/CronJobDrawer.tsx`](https://github.com/iOfficeAI/AionUi/blob/main/src/renderer/pages/cron/components/CronJobDrawer.tsx)): Form interface for creating and editing job names, schedules, and message content
- **cronUtils.ts** ([`src/renderer/pages/cron/utils/cronUtils.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/renderer/pages/cron/utils/cronUtils.ts)): Helper functions `formatSchedule`, `formatNextRun`, and `getJobStatusFlags` for human-readable display

## Code Examples

### Creating a Scheduled Task from the Renderer

```typescript
import { ipcBridge } from '@/common';
import { v4 as uuid } from 'uuid';

async function createDailyReminder(conversationId: string) {
  const job = await ipcBridge.cron.addJob.invoke({
    name: 'Morning Briefing',
    schedule: {
      kind: 'cron',
      expr: '0 9 * * *',
      tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
      description: 'Every day at 09:00',
    },
    message: 'Good morning! Here is your daily summary.',
    conversationId,
    agentType: 'openai',
    createdBy: 'user',
  });

  console.log('Cron job created:', job.id);
}

```

This IPC call routes to `CronService.addJob` in [`src/process/services/cron/CronService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronService.ts) (lines 70-126), which persists the job and initializes the native timer.

### Using the useCronJobs Hook

```tsx
import { useCronJobs } from '@/renderer/pages/cron/hooks/useCronJobs';
import { Button } from '@arco-design/web-react';

function CronControl({ conversationId }: { conversationId: string }) {
  const { jobs, hasJobs, pauseJob, resumeJob, deleteJob } = useCronJobs(conversationId);

  if (!hasJobs) return null;
  const job = jobs[0];

  return (
    <div>
      <span>{job.name} (next: {new Date(job.state.nextRunAtMs!).toLocaleString()})</span>
      {job.enabled ? (
        <Button onClick={() => pauseJob(job.id)}>Pause</Button>
      ) : (
        <Button onClick={() => resumeJob(job.id)}>Resume</Button>
      )}
      <Button status="danger" onClick={() => deleteJob(job.id)}>Delete</Button>
    </div>
  );
}

```

The hook subscribes to `ipcBridge.cron.onJobUpdated` events (see [`useCronJobs.ts`](https://github.com/iOfficeAI/AionUi/blob/main/useCronJobs.ts) lines 92-163), ensuring the UI reflects backend state changes immediately.

### Handling Missed Executions

When the system resumes from sleep, `CronService.handleSystemResume` (lines 55-92) automatically detects missed jobs and inserts warning messages into the conversation. No manual intervention is required:

```typescript
// This happens automatically in the main process
// CronService.handleSystemResume detects missed executions
// and calls insertMissedJobMessage for each missed job

```

The warning appears as a standard message in the chat history, alerting users that a scheduled task fired while the system was offline.

## Summary

- **AionUi's cron subsystem** consists of four layers: SQLite persistence ([`CronStore.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronStore.ts)), timer management ([`CronService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronService.ts)), concurrency protection ([`CronBusyGuard.ts`](https://github.com/iOfficeAI/AionUi/blob/main/CronBusyGuard.ts)), and React frontend hooks ([`useCronJobs.ts`](https://github.com/iOfficeAI/AionUi/blob/main/useCronJobs.ts)).
- **Job scheduling** supports three timer types: standard cron expressions (`kind: 'cron'`), intervals (`kind: 'every'`), and one-time executions (`kind: 'at'`).
- **Power management** uses Electron's `powerSaveBlocker` to prevent app suspension while active jobs exist, and `handleSystemResume` catches up on missed executions after system sleep.
- **Busy-guard logic** prevents race conditions by checking `cronBusyGuard.isProcessing()` before firing jobs, with automatic 30-second retries when conversations are processing other messages.
- **Real-time synchronization** occurs through IPC events (`onJobUpdated`, `onJobCreated`, `onJobRemoved`) that keep the React UI in sync with the backend state without polling.

## Frequently Asked Questions

### How does AionUi persist scheduled tasks between app restarts?

AionUi stores all cron job definitions in a local SQLite database through the `CronStore` class in [`src/process/services/cron/CronStore.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronStore.ts). When the application initializes, `CronService.init()` (lines 45-63) loads all enabled jobs from `cronStore.listEnabled()` and recreates their native timers. This ensures that scheduled tasks survive app restarts and system reboots without requiring external services.

### What happens if a scheduled task fires while the conversation is busy processing another message?

The `CronBusyGuard` in [`src/process/services/cron/CronBusyGuard.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/services/cron/CronBusyGuard.ts) tracks per-conversation processing flags. Before executing a job, `CronService.executeJob()` (lines 90-140) checks `cronBusyGuard.isProcessing()`. If the conversation is busy, the service increments the job's `retryCount` and schedules a retry in 30 seconds. This prevents message collisions while ensuring the task eventually executes once the conversation becomes idle.

### Can AionUi handle scheduled tasks that were missed while the computer was asleep?

Yes. The `CronService` implements system resume detection in `handleSystemResume()` (lines 55-92). When the system wakes from sleep, this method identifies jobs that should have fired during the offline period. For each missed execution, it inserts a warning message into the conversation via `insertMissedJobMessage()` and restarts the job timers. Additionally, `updatePowerBlocker()` (lines 32-51) uses Electron's `powerSaveBlocker.start('prevent-app-suspension')` to minimize missed executions by keeping the app alive while jobs are active.

### How do I create a recurring daily task from the React frontend?

Use the `ipcBridge.cron.addJob.invoke` method with `kind: 'cron'` in the schedule object. The `useCronJobs` hook in [`src/renderer/pages/cron/hooks/useCronJobs.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/renderer/pages/cron/hooks/useCronJobs.ts) provides a React-friendly abstraction, but you can also call the IPC bridge directly as shown in the code example above. Pass a standard cron expression (e.g., `'0 9 * * *'` for 9:00 AM daily), the target `conversationId`, and the message content. The `CronService.addJob()` method in the main process will persist the job to SQLite and initialize the native timer immediately.