Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Scheduler

Cron and interval-based job scheduling for Synaptic agents. The synaptic-scheduler crate provides a Scheduler trait and a Tokio-backed implementation for running periodic tasks such as health checks, data syncs, or scheduled agent invocations.

Setup

Add the scheduler feature to your Cargo.toml:

[dependencies]
synaptic = { version = "0.4", features = ["scheduler"] }

SchedulerTask Trait

Every scheduled job must implement the SchedulerTask trait:

use async_trait::async_trait;
use synaptic::scheduler::SchedulerTask;

struct MyTask {
    label: String,
}

#[async_trait]
impl SchedulerTask for MyTask {
    async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        println!("[{}] Running scheduled task", self.label);
        // Perform your work here
        Ok(())
    }
}

The run method is called each time the scheduler fires the job. If it returns an error, the scheduler logs it and continues scheduling future runs.

TokioScheduler

TokioScheduler is the built-in implementation backed by Tokio tasks and timers. Each job runs in its own spawned task.

use synaptic::scheduler::{TokioScheduler, Scheduler};

let scheduler = TokioScheduler::new();

Scheduling with a Cron Expression

use synaptic::scheduler::{TokioScheduler, Scheduler, SchedulerTask};

let scheduler = TokioScheduler::new();

let task = Box::new(MyTask { label: "cron-job".to_string() });
let job_id = scheduler.schedule_cron("*/5 * * * *", "every-5-min", task).await?;
println!("Scheduled job: {}", job_id);

Scheduling with a Fixed Interval

use std::time::Duration;
use synaptic::scheduler::{TokioScheduler, Scheduler};

let scheduler = TokioScheduler::new();

let task = Box::new(MyTask { label: "interval-job".to_string() });
let job_id = scheduler
    .schedule_interval(Duration::from_secs(30), "every-30s", task)
    .await?;

Managing Jobs

// List all registered jobs
let jobs = scheduler.list_jobs().await;
for job in &jobs {
    println!("{}: {} (runs: {})", job.id, job.name, job.run_count);
}

// Cancel a specific job
scheduler.cancel(&job_id).await?;

// Shut down all jobs
scheduler.shutdown().await;

Cron Expression Reference

The built-in cron parser supports a subset of standard 5-field expressions:

ExpressionDescription
*/5 * * * *Every 5 minutes
*/15 * * * *Every 15 minutes
0 * * * *Every hour (at minute 0)
0 0 * * *Daily at midnight
0 0 * * 0Weekly on Sunday at midnight
0 0 * * 1Weekly on Monday at midnight

The fields are: minute hour day-of-month month day-of-week. Unsupported or complex expressions (e.g. ranges, lists) will return a SynapticError::Config error at scheduling time.

JobInfo

The list_jobs() method returns Vec<JobInfo> with metadata about each registered job:

FieldTypeDescription
idJobIdUnique identifier (UUID)
nameStringHuman-readable job name
scheduleScheduleKindCron(expr) or Interval(dur)
next_runOption<Instant>Estimated next execution time
run_countu64Number of completed runs

Example: Periodic Health Check Agent

A complete example that runs a health-check agent every 5 minutes:

use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use synaptic::core::{ChatModel, ChatRequest, Message};
use synaptic::openai::OpenAiChatModel;
use synaptic::scheduler::{TokioScheduler, Scheduler, SchedulerTask};

struct HealthCheckTask {
    model: Arc<dyn ChatModel>,
}

#[async_trait]
impl SchedulerTask for HealthCheckTask {
    async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let request = ChatRequest::new(vec![
            Message::system("You are a health check assistant. Summarize system status."),
            Message::human("Check that all services are operational."),
        ]);

        let response = self.model.chat(&request).await?;
        println!("Health check result: {}", response.message.content());
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let model: Arc<dyn ChatModel> = Arc::new(OpenAiChatModel::new("gpt-4o-mini"));
    let scheduler = TokioScheduler::new();

    let task = Box::new(HealthCheckTask { model });
    scheduler.schedule_cron("*/5 * * * *", "health-check", task).await?;

    println!("Scheduler running. Press Ctrl+C to stop.");
    tokio::signal::ctrl_c().await?;

    scheduler.shutdown().await;
    Ok(())
}

Example: Multiple Jobs

use std::time::Duration;
use synaptic::scheduler::{TokioScheduler, Scheduler};

let scheduler = TokioScheduler::new();

// Fast polling job
let fast_task = Box::new(MyTask { label: "fast-poll".to_string() });
scheduler
    .schedule_interval(Duration::from_secs(10), "fast-poll", fast_task)
    .await?;

// Hourly summary job
let hourly_task = Box::new(MyTask { label: "hourly-summary".to_string() });
scheduler
    .schedule_cron("0 * * * *", "hourly-summary", hourly_task)
    .await?;

// Check what is running
let jobs = scheduler.list_jobs().await;
assert_eq!(jobs.len(), 2);