Job Extension
Add background jobs and scheduled tasks to your extension.
On this page
Extensions add background tasks via the jobs() method.
Jobs Method
fn jobs(&self) -> Vec<Arc<dyn Job>> {
vec![
Arc::new(CleanupJob),
Arc::new(SyncJob),
]
}
Job Trait
use systemprompt_provider_contracts::{Job, JobContext, JobResult};
#[derive(Debug, Clone, Copy, Default)]
pub struct CleanupJob;
#[async_trait]
impl Job for CleanupJob {
fn name(&self) -> &'static str {
"cleanup"
}
fn description(&self) -> &'static str {
"Clean up expired records"
}
fn schedule(&self) -> &'static str {
"0 0 * * * *" // Every hour
}
fn run_on_startup(&self) -> bool {
false // Set to true to run once at scheduler startup
}
async fn execute(&self, ctx: &JobContext) -> anyhow::Result<JobResult> {
let pool = ctx.db_pool::<Arc<PgPool>>()
.ok_or_else(|| anyhow::anyhow!("Database not available"))?;
let deleted = sqlx::query!(
"DELETE FROM temp_records WHERE expires_at < NOW()"
)
.execute(&*pool)
.await?
.rows_affected();
Ok(JobResult::success().with_message(format!("Deleted {} records", deleted)))
}
}
Startup Jobs
Jobs can run immediately when the scheduler starts using run_on_startup():
fn run_on_startup(&self) -> bool {
true // Runs once at startup, then follows cron schedule
}
Important: Jobs only run on startup if BOTH conditions are met:
run_on_startup()returnstruein the job implementation- Job is listed in
services/scheduler/config.yamlwithenabled: true
This two-layer design separates developer defaults from operations control.
Cron Schedule Format
6-field cron expression: second minute hour day-of-month month day-of-week
┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12)
│ │ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │ │
* * * * * *
Examples:
0 0 * * * *- Every hour at minute 00 */15 * * * *- Every 15 minutes0 0 0 * * *- Daily at midnight0 30 2 * * *- Daily at 2:30 AM0 0 0 * * 1- Every Monday at midnight
JobContext
async fn execute(&self, ctx: &JobContext) -> anyhow::Result<JobResult> {
// Get database pool
let pool = ctx.db_pool::<Arc<PgPool>>()?;
// Get configuration
let config = ctx.config();
// Access other services
// ...
}
JobResult
// Success
Ok(JobResult::success())
Ok(JobResult::success().with_message("Processed 100 items"))
// Failure (will be retried based on configuration)
Err(anyhow::anyhow!("Database connection failed"))
Configuration Override
Override job schedules in profile.yaml:
scheduler:
jobs:
- extension: my-extension
job: cleanup
schedule: "0 */30 * * * *" # Override to every 30 minutes
enabled: true
CLI Commands
# Run job manually
systemprompt infra jobs run cleanup
# List all jobs
systemprompt infra jobs list
# Show job status
systemprompt infra jobs status cleanup
Typed Extension
use systemprompt::extension::prelude::JobExtensionTyped;
impl JobExtensionTyped for MyExtension {
fn jobs(&self) -> Vec<Arc<dyn Job>> {
vec![Arc::new(CleanupJob)]
}
}