# How the OpenHuman Cron Job System Schedules and Delivers Background Tasks

> Discover how the OpenHuman cron job system schedules and delivers background tasks. Learn about SQLite persistence, execution methods, and output routing via events and webhooks.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: internals
- Published: 2026-05-12

---

**The OpenHuman cron job system persists job definitions in SQLite, evaluates schedules to compute next run times, executes due jobs via shell commands or the QuickJS runtime, and delivers outputs through a domain event bus that routes to configured channels or webhooks.**

The OpenHuman cron job system provides a native background-task subsystem that runs inside the core side-car process of the `tinyhumansai/openhuman` repository. It combines persistent storage with async scheduling to execute both shell commands and AI agent tasks, using a domain-event architecture to deliver results to chat channels, webhooks, or desktop notifications.

## System Architecture and Data Flow

The cron implementation follows an eight-step pipeline orchestrated by the async entry point `scheduler::run` in [`src/openhuman/cron/scheduler.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/scheduler.rs). This function starts on **SystemStartup** (line 27) and runs until the core process exits.

1. **Job definitions** are stored in the SQLite workspace via [`src/openhuman/cron/store.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/store.rs).
2. **Schedule evaluation** converts cron expressions or intervals into concrete `next_run` timestamps using [`src/openhuman/cron/schedule.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/schedule.rs).
3. **Due-job detection** queries `store::due_jobs` for jobs where `next_run` ≤ *now*.
4. **Execution** handles shell commands via `std::process::Command` or agent jobs via the QuickJS runtime (`quickjs::Engine::run_agent`).
5. **Result recording** writes a `CronRun` entry via `store::record_run`.
6. **Rescheduling** recalculates `next_run` via `store::reschedule_after_run` or deletes one-shot jobs.
7. **Delivery publishing** emits a `DomainEvent::CronDeliveryRequested` containing `DeliveryConfig` metadata.
8. **Event routing** consumes the event in [`src/openhuman/cron/bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/bus.rs) and forwards output to the target channel.

## Storing and Retrieving Cron Jobs

All job state lives in a SQLite table managed by [`src/openhuman/cron/store.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/store.rs). The module provides CRUD helpers such as `add_job`, `add_shell_job`, `add_agent_job`, `list_jobs`, and `remove_job`.

Each row maps to a `CronJob` struct defined in [`src/openhuman/cron/types.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/types.rs), containing:
- A unique job ID
- A `Schedule` variant (cron expression, fixed interval, or one-shot)
- Command strings or agent prompts
- A `DeliveryConfig` specifying output destination and mode

The scheduler polls for work using `store::due_jobs`, which executes `SELECT * FROM cron_jobs WHERE next_run ≤ :now` to identify tasks ready for execution.

## Schedule Parsing and Validation

Before a job can run, its schedule must be normalized to a concrete timestamp. The [`src/openhuman/cron/schedule.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/schedule.rs) module handles this via `next_run_for_schedule`, `validate_schedule`, and `normalize_expression`.

These functions accept human-readable inputs—standard cron syntax (e.g., `*/5 * * * *`), fixed durations, or absolute timestamps—and return a `DateTime<Utc>` stored in the `next_run` column. This approach allows the main loop to perform simple timestamp comparisons rather than parsing cron syntax on every tick.

## The Execution Loop and Job Types

The core async loop resides in [`src/openhuman/cron/scheduler.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/scheduler.rs). When `execute_job_now` is invoked, it distinguishes between two execution modes:

- **Shell jobs**: Spawn a subprocess using `std::process::Command` with `sh`, capturing stdout and stderr.
- **Agent jobs**: Invoke the QuickJS skill runtime (`openhuman::cron::ops::add_agent_job`), which may call LLM prompts and return generated content.

Both variants return a `(bool success, String output)` tuple that feeds into the recording and delivery phases.

## Recording Results and Rescheduling

After execution, `store::record_run` inserts a row into the `cron_runs` table and updates the parent job's `last_*` fields to track history. The system then calls `store::reschedule_after_run` to determine the job's next state:

- For repeating jobs, it recomputes `next_run` based on the original schedule.
- For one-shot jobs or when `delete_after_run` is set, it removes the job from the database.

## Delivering Outputs via the Event Bus

Output delivery decouples execution from transport via the domain event bus. The `scheduler::deliver_job` function constructs a `DomainEvent::CronDeliveryRequested` containing:
- The job output string
- A `DeliveryConfig` struct (mode, channel, `to` address, `best_effort` flag)
- Metadata linking back to the original `CronJob`

The `CronDeliverySubscriber` in [`src/openhuman/cron/bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/bus.rs) listens for these events and routes messages to the appropriate driver—whether that's a chat channel handler, a webhook dispatcher, or the `announce` broadcast for desktop UI notifications.

## Practical Example: Creating a Cron Job

The following Rust code demonstrates adding a shell job and forcing immediate execution:

```rust
use openhuman::cron::{self, Schedule};

// Create a repeating shell job that echoes every 5 minutes
let cfg = &config; // core Config instance
let job = cron::add_shell_job(
    cfg,
    "my-hello-job",
    Schedule::Cron { expr: "*/5 * * * *".into() },
    "echo hello",
)?;

// Force-run immediately for testing
let (success, output) = cron::scheduler::execute_job_now(cfg, &job).await;
println!("Run success={}, output={}", success, output);

// On the next scheduler tick, due_jobs will detect this task,
// execute it, and publish CronDeliveryRequested for the bus subscriber.

```

Additional startup-time seeding occurs in [`src/openhuman/cron/seed.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/seed.rs), which emits `DomainEvent::ProactiveMessageRequested` for agent-initiated conversations.

## Summary

- **Persistence**: Jobs live in SQLite ([`store.rs`](https://github.com/tinyhumansai/openhuman/blob/main/store.rs)) with CRUD operations for shell and agent tasks.
- **Scheduling**: Cron expressions and intervals are parsed to concrete timestamps ([`schedule.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schedule.rs)), queried via `due_jobs`.
- **Execution**: An async loop ([`scheduler.rs`](https://github.com/tinyhumansai/openhuman/blob/main/scheduler.rs)) runs shell commands or QuickJS agents and captures output.
- **Rescheduling**: Completed jobs are either rescheduled or deleted based on their configuration.
- **Delivery**: Results travel through a domain event bus ([`bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/bus.rs)) using `CronDeliveryRequested` events, routing to channels or webhooks defined in `DeliveryConfig`.

## Frequently Asked Questions

### How does OpenHuman persist cron job definitions?

OpenHuman stores cron jobs in an SQLite table accessed via [`src/openhuman/cron/store.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/store.rs). Helper functions like `add_shell_job` and `add_agent_job` write `CronJob` structs containing schedules, commands, and delivery configurations to the database, where the `next_run` column indexes upcoming executions.

### What types of tasks can the OpenHuman cron system execute?

The system supports two task types defined in [`src/openhuman/cron/scheduler.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/scheduler.rs): **shell jobs** that spawn subprocesses via `std::process::Command`, and **agent jobs** that execute JavaScript/TypeScript skills in the QuickJS runtime (`quickjs::Engine::run_agent`), enabling LLM-based workflows.

### How are task outputs delivered to users or systems?

After execution, `scheduler::deliver_job` publishes a `DomainEvent::CronDeliveryRequested` event containing the output and a `DeliveryConfig`. The `CronDeliverySubscriber` in [`src/openhuman/cron/bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/bus.rs) consumes this event and routes the message to chat channels, webhooks, or desktop announcements based on the configuration's `mode` and `channel` fields.

### When does the scheduler evaluate and run due jobs?

The scheduler evaluates jobs continuously within the async `scheduler::run` loop, which starts on **SystemStartup** in [`src/openhuman/cron/scheduler.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/scheduler.rs) (line 27). On each iteration, it queries `store::due_jobs` for records where `next_run` is less than or equal to the current UTC timestamp, executing all matches before sleeping until the next cycle.