The AgentScheduler enables automated, recurring execution of agents on a schedule using cron expressions or fixed intervals.
Overview
AgentScheduler provides:
Cron-based scheduling - Run agents on complex schedules (“daily at 9 AM”, “every Monday”, etc.)
Interval-based scheduling - Run agents every N milliseconds
Lifecycle callbacks - React to job triggers, completions, and errors
Event streaming - Stream events as jobs execute
Execution tracking - Monitor runs, errors, and schedules
Pause/resume - Control job execution dynamically
Quick Start
import { AgentScheduler , AgentBuilder } from "@iqai/adk" ;
// Build an agent
const { runner } = await AgentBuilder
. create ( "daily-reporter" )
. withModel ( "gemini-2.5-flash" )
. withInstruction ( "Generate a daily summary report" )
. build ();
// Create scheduler
const scheduler = new AgentScheduler ();
// Schedule the agent
scheduler . schedule ({
id: "daily-report" ,
cron: "0 9 * * *" , // Every day at 9:00 AM
runner ,
userId: "system" ,
input: "Generate today's report" ,
onComplete : ( id , events ) => {
console . log ( `Report generated with ${ events . length } events` );
},
});
// Start the scheduler
scheduler . start ();
// Later: stop the scheduler
await scheduler . stop ();
Cron Expressions
ADK uses standard cron syntax with 5 fields:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 7, 0 or 7 is Sunday)
│ │ │ │ │
│ │ │ │ │
* * * * *
Common Patterns
Every minute
Every hour
Daily at specific time
Weekly on Monday
First of month
Business hours
scheduler . schedule ({
id: "every-minute" ,
cron: "* * * * *" ,
// ...
});
ADK uses the croner library for cron parsing with robust error handling and timezone support.
Interval-based Scheduling
Alternatively, schedule jobs at fixed intervals:
scheduler . schedule ({
id: "every-30-seconds" ,
intervalMs: 30_000 , // 30 seconds
runner ,
userId: "system" ,
input: "Check for new messages" ,
});
You must provide either cron OR intervalMs, but not both.
Scheduled Job Configuration
interface ScheduledJob {
/** Unique identifier for this job */
id : string ;
/** Cron expression (e.g., "0 9 * * *" for daily at 9 AM) */
cron ?: string ;
/** Interval in milliseconds (alternative to cron) */
intervalMs ?: number ;
/** The runner to execute */
runner : EnhancedRunner < any , any >;
/** User ID for the session */
userId : string ;
/** Session ID (optional - creates new session each run if not provided) */
sessionId ?: string ;
/** Input message to send on each scheduled run */
input : string | Content ;
/** Whether the job is enabled (default: true) */
enabled ?: boolean ;
/** Maximum number of executions (undefined = unlimited) */
maxExecutions ?: number ;
/** Callback when job triggers */
onTrigger ?: ( jobId : string ) => void ;
/** Callback when execution completes */
onComplete ?: ( jobId : string , events : Event []) => void ;
/** Callback on execution error */
onError ?: ( jobId : string , error : Error ) => void ;
/** Callback for each event as it streams in during execution */
onEvent ?: ( jobId : string , event : Event ) => void ;
}
Lifecycle Callbacks
React to job execution events:
scheduler . schedule ({
id: "monitored-job" ,
cron: "0 * * * *" ,
runner ,
userId: "system" ,
input: "Run task" ,
onTrigger : ( jobId ) => {
console . log ( `[ ${ jobId } ] Starting execution...` );
},
onEvent : ( jobId , event ) => {
// Called for each event during execution
if ( event . isFinalResponse ()) {
console . log ( `[ ${ jobId } ] Final response:` , event . text );
}
},
onComplete : ( jobId , events ) => {
console . log ( `[ ${ jobId } ] Completed with ${ events . length } events` );
// Send notification, update database, etc.
},
onError : ( jobId , error ) => {
console . error ( `[ ${ jobId } ] Failed:` , error . message );
// Alert on-call, retry logic, etc.
},
});
Managing Jobs
Pause and Resume
// Pause a job
scheduler . pause ( "daily-report" );
// Resume a paused job
scheduler . resume ( "daily-report" );
Unschedule
// Remove a job completely
scheduler . unschedule ( "daily-report" );
Trigger Manually
Run a scheduled job outside its normal schedule:
// Trigger and wait for completion
const events = await scheduler . triggerNow ( "daily-report" );
console . log ( "Manual trigger completed:" , events );
// Or stream events as they occur
for await ( const event of scheduler . triggerNowStream ( "daily-report" )) {
console . log ( "Event:" , event );
}
Job Status
const status = scheduler . getJobStatus ( "daily-report" );
console . log ( status );
// {
// enabled: true,
// isRunning: false,
// executionCount: 42,
// lastRunTime: 1234567890,
// nextRunTime: 1234571490,
// lastError: undefined
// }
// List all job IDs
const jobIds = scheduler . getJobIds ();
console . log ( "Scheduled jobs:" , jobIds );
Scheduler Events
Listen to scheduler-level events:
scheduler . addEventListener (( event ) => {
console . log ( "Scheduler event:" , event . type , event . scheduleId );
});
// Event types:
// - "schedule:triggered" - Job started
// - "schedule:completed" - Job finished successfully
// - "schedule:failed" - Job encountered an error
// - "schedule:paused" - Job was paused
// - "schedule:resumed" - Job was resumed
Advanced Examples
Limited Execution Count
Run a job a specific number of times:
scheduler . schedule ({
id: "onboarding-reminder" ,
intervalMs: 24 * 60 * 60 * 1000 , // Daily
runner ,
userId: "new-user-123" ,
input: "Send onboarding reminder" ,
maxExecutions: 7 , // Only run for 7 days
});
After 7 executions, the job automatically pauses.
Persistent Sessions
Maintain conversation state across scheduled runs:
const sessionId = "persistent-session-123" ;
scheduler . schedule ({
id: "conversational-job" ,
cron: "0 */4 * * *" , // Every 4 hours
runner ,
userId: "system" ,
sessionId , // Reuse same session
input: "Continue the conversation" ,
});
Multiple Scheduled Agents
const scheduler = new AgentScheduler ();
// Morning reports
scheduler . schedule ({
id: "morning-summary" ,
cron: "0 8 * * 1-5" , // Weekdays at 8 AM
runner: morningReporter ,
userId: "system" ,
input: "Generate morning summary" ,
});
// Afternoon updates
scheduler . schedule ({
id: "afternoon-update" ,
cron: "0 14 * * 1-5" , // Weekdays at 2 PM
runner: afternoonReporter ,
userId: "system" ,
input: "Generate afternoon update" ,
});
// Nightly cleanup
scheduler . schedule ({
id: "nightly-cleanup" ,
cron: "0 2 * * *" , // Daily at 2 AM
runner: cleanupAgent ,
userId: "system" ,
input: "Run cleanup tasks" ,
});
scheduler . start ();
Dynamic Scheduling
Schedule jobs based on runtime conditions:
const scheduler = new AgentScheduler ();
scheduler . start ();
// Later in your application...
function scheduleUserReminder ( userId : string , time : string ) {
scheduler . schedule ({
id: `reminder- ${ userId } ` ,
cron: time , // e.g., "0 9 * * *"
runner: reminderAgent ,
userId ,
input: "Send daily reminder" ,
onComplete : ( jobId , events ) => {
// Track completion in database
db . recordReminder ( userId , events );
},
});
}
// User signs up
scheduleUserReminder ( "user-123" , "0 9 * * *" );
// User unsubscribes
scheduler . unschedule ( "reminder-user-123" );
Error Handling
Scheduler handles errors gracefully:
scheduler . schedule ({
id: "robust-job" ,
cron: "0 * * * *" ,
runner ,
userId: "system" ,
input: "Run task" ,
onError : async ( jobId , error ) => {
console . error ( `Job ${ jobId } failed:` , error );
// Send alert
await notifyOncall ({
message: `Scheduled job ${ jobId } failed` ,
error: error . message ,
});
// Don't throw - job will retry on next schedule
},
});
Failed jobs don’t stop the scheduler. The job will retry on its next scheduled time.
Preventing Overlapping Executions
Scheduler automatically prevents overlaps:
scheduler . schedule ({
id: "long-running-job" ,
intervalMs: 60_000 , // Every minute
runner: slowAgent , // Takes 3 minutes to complete
userId: "system" ,
input: "Run slow task" ,
});
// If job is still running when next interval hits,
// the new execution is skipped (logged as warning)
Graceful Shutdown
process . on ( "SIGTERM" , async () => {
console . log ( "Shutting down scheduler..." );
// Stop accepting new executions
await scheduler . stop ();
// Waits up to 30 seconds for running jobs to complete
console . log ( "Scheduler stopped gracefully" );
process . exit ( 0 );
});
Best Practices
Use unique job IDs - Job IDs must be unique. Consider prefixing with a namespace (e.g., user-${userId}-reminder).
Set reasonable intervals - Don’t schedule jobs too frequently. LLMs and tools have rate limits.
Handle errors gracefully - Always implement onError callbacks to handle failures.
Monitor execution times - Track job duration to ensure schedules don’t overlap.
Use persistent sessions carefully - Reusing sessions can grow memory over time. Consider ending sessions periodically.
Scheduled jobs run indefinitely until stopped. Ensure proper cleanup in long-running applications.
Cron schedules use the system’s local timezone. Consider timezone implications for distributed systems.
See Also