Automate phone calls on a schedule using cron expressions with the built-in APScheduler integration.
Overview
The scheduler enables:
✅ Recurring calls (daily, weekly, monthly)
✅ Multiple calls per schedule
✅ Cron-based timing (flexible scheduling)
✅ Metadata for tracking and analytics
✅ Enable/disable schedules without deleting
✅ Manual execution for testing
Reference: src/agenticai/scheduler/scheduler.py
Configuration File
Schedules are defined in schedules.yaml at your project root.
Example Configuration
schedules :
- name : "morning_check"
cron : "0 9 * * 1-5" # 9 AM, Monday-Friday
enabled : true
calls :
- to_number : "+15551234567"
prompt : "Good morning check-in call. Ask about any updates or concerns."
metadata :
purpose : "daily_checkin"
priority : "normal"
- name : "appointment_reminder"
cron : "0 8 * * *" # 8 AM daily
enabled : true
calls :
- to_number : "+15559876543"
prompt : "Call to remind about the scheduled appointment today."
metadata :
purpose : "reminder"
priority : "high"
- name : "weekly_report"
cron : "0 17 * * 5" # 5 PM on Fridays
enabled : false
calls :
- to_number : "+15551112222"
prompt : "Weekly status report call. Summarize this week's activities."
metadata :
purpose : "report"
priority : "normal"
Reference: ~/workspace/source/schedules.yaml
Schedule Structure
Field Required Description nameYes Unique identifier for the schedule cronYes Cron expression for timing enabledYes Whether the schedule is active callsYes Array of calls to make calls[].to_numberYes Phone number in E.164 format calls[].promptYes AI instructions for the call calls[].metadataNo Custom tracking data
Schedules use standard cron syntax:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
│ │ │ │ │
* * * * *
Common Patterns
Pattern Description Cron Expression Every day at 9 AM Daily morning call 0 9 * * *Weekdays at 8:30 AM Monday-Friday 30 8 * * 1-5Every Monday at 10 AM Weekly 0 10 * * 1First day of month Monthly 0 9 1 * *Every 2 hours Recurring 0 */2 * * *Fridays at 5 PM End of week 0 17 * * 5Specific date/time Jan 1 at noon 0 12 1 1 *
Interactive Cron Builder
Use crontab.guru to build and validate expressions.
CLI Commands
List All Schedules
View configured schedules:
Output:
┌────────────────────────────────────────────────────────┐
│ Configured Schedules │
├─────────────────┬────────────┬─────────┬───────────────┤
│ Name │ Cron │ Enabled │ Calls │
├─────────────────┼────────────┼─────────┼───────────────┤
│ morning_check │ 0 9 * * 1-5│ Yes │ 1 │
│ appointment_... │ 0 8 * * * │ Yes │ 1 │
│ weekly_report │ 0 17 * * 5 │ No │ 1 │
└─────────────────┴────────────┴─────────┴───────────────┘
Reference: src/agenticai/cli.py:109-131
Run a Schedule Manually
Test a schedule immediately without waiting:
agenticai schedule run morning_check --webhook-url https://your-url.ngrok.io
Output:
Running schedule: morning_check
Initiated 1 call(s)
Call ID: CA1234567890abcdef
Waiting for calls to complete... (Ctrl+C to exit)
Reference: src/agenticai/cli.py:133-177
Manual runs ignore the enabled flag - they run even if disabled.
Running the Scheduler
Option 1: Daemon Mode
Run server with scheduler in foreground:
agenticai daemon --webhook-url https://your-url.ngrok.io
Output:
Starting Agentic AI in daemon mode
Scheduler started
morning_check: 2024-03-16 09:00:00
appointment_reminder: 2024-03-16 08:00:00
INFO: Uvicorn running on http://0.0.0.0:8080
Reference: src/agenticai/cli.py:257-308
Option 2: Background Service (Recommended)
Install as a persistent service:
agenticai service install --webhook-url https://your-permanent-url.com
Check status to see next run times:
Recent logs:
[2024-03-15 14:32:02] INFO: Scheduler started
[2024-03-15 14:32:02] INFO: Next run: morning_check at 2024-03-16 09:00:00
[2024-03-15 14:32:02] INFO: Next run: appointment_reminder at 2024-03-16 08:00:00
See the Service Management guide for details.
Scheduler Internals
Loading Schedules
The scheduler loads schedules.yaml on startup:
scheduler = CallScheduler(call_handler)
scheduler.load_schedules()
Reference: src/agenticai/scheduler/scheduler.py:45-49
Starting the Scheduler
scheduler.start()
# Only enabled schedules are added as jobs
Reference: src/agenticai/scheduler/scheduler.py:51-73
Job Execution
When a schedule triggers:
Job fires at the cron time
Calls initiated for each call in the schedule
Metadata passed to each call session
Errors logged if calls fail (doesn’t stop other calls)
Reference: src/agenticai/scheduler/scheduler.py:129-163
Multiple Calls Per Schedule
A schedule can initiate multiple calls:
schedules :
- name : "team_standup"
cron : "0 10 * * 1-5" # 10 AM weekdays
enabled : true
calls :
- to_number : "+15551111111"
prompt : "Ask Alice about her progress on the mobile app."
metadata :
team_member : "alice"
role : "developer"
- to_number : "+15552222222"
prompt : "Ask Bob about the server deployment status."
metadata :
team_member : "bob"
role : "devops"
- to_number : "+15553333333"
prompt : "Ask Carol about the design mockups."
metadata :
team_member : "carol"
role : "designer"
All three calls will be initiated simultaneously when the schedule triggers.
Metadata helps track and analyze calls:
metadata :
purpose : "appointment_reminder"
priority : "high"
customer_id : "CUST123"
campaign : "spring-2024"
follow_up_required : true
Access metadata in your application:
# Metadata is available in the call session
session = call_manager.active_sessions[call_id]
metadata = session.metadata
print (metadata[ "purpose" ]) # "appointment_reminder"
print (metadata[ "priority" ]) # "high"
Dynamic Schedules
You can programmatically add schedules:
from agenticai.scheduler.scheduler import CallScheduler
from apscheduler.triggers.cron import CronTrigger
# Create scheduler
scheduler = CallScheduler(call_handler)
# Add a dynamic job
scheduler._scheduler.add_job(
scheduler._execute_schedule,
trigger = CronTrigger( hour = 14 , minute = 30 ),
id = "dynamic_afternoon_call" ,
kwargs = {
"schedule" : {
"name" : "afternoon_call" ,
"calls" : [
{
"to_number" : "+15551234567" ,
"prompt" : "Afternoon check-in" ,
"metadata" : { "dynamic" : True }
}
]
}
}
)
Reference: src/agenticai/scheduler/scheduler.py:117-127
Monitoring Schedules
View Next Run Times
Programmatically check when schedules will next run:
scheduler = CallScheduler(call_handler)
scheduler.load_schedules()
scheduler.start()
next_runs = scheduler.get_next_run_times()
for name, time in next_runs.items():
print ( f " { name } : { time } " )
Output:
morning_check: 2024-03-16 09:00:00
appointment_reminder: 2024-03-16 08:00:00
Reference: src/agenticai/scheduler/scheduler.py:217-227
List Schedule Details
schedules = scheduler.list_schedules()
for s in schedules:
print ( f " { s[ 'name' ] } : { s[ 'cron' ] } ( { s[ 'call_count' ] } calls)" )
Reference: src/agenticai/scheduler/scheduler.py:201-215
Best Practices
Step 1: Test Manually First
Before enabling a schedule:
# Run it manually to verify
agenticai schedule run morning_check --webhook-url https://your-url.ngrok.io
Step 2: Start with enabled: false
Create schedules disabled initially:
schedules :
- name : "new_schedule"
cron : "0 9 * * *"
enabled : false # Test first!
calls : [ ... ]
Step 3: Use Descriptive Names
Names should explain the purpose:
# Good
- name : "daily_customer_satisfaction_survey"
# Bad
- name : "schedule1"
Track everything you might need:
metadata :
purpose : "appointment_reminder"
campaign_id : "Q1-2024"
department : "sales"
customer_tier : "premium"
follow_up_required : true
created_by : "scheduler"
version : "1.0"
Watch scheduler activity:
agenticai service logs -f | grep "schedule"
Advanced Examples
Business Hours Only
schedules :
- name : "business_hours_reminder"
cron : "0 9-17 * * 1-5" # Every hour, 9 AM - 5 PM, weekdays
enabled : true
calls :
- to_number : "+15551234567"
prompt : "Hourly reminder during business hours"
Specific Days
schedules :
- name : "monday_wednesday_friday"
cron : "0 14 * * 1,3,5" # 2 PM on Mon, Wed, Fri
enabled : true
calls :
- to_number : "+15551234567"
prompt : "Alternate day check-in"
Every 15 Minutes
schedules :
- name : "frequent_updates"
cron : "*/15 * * * *" # Every 15 minutes
enabled : true
calls :
- to_number : "+15551234567"
prompt : "Frequent status check"
Quarterly Reports
schedules :
- name : "quarterly_review"
cron : "0 9 1 1,4,7,10 *" # 9 AM on Jan 1, Apr 1, Jul 1, Oct 1
enabled : true
calls :
- to_number : "+15551234567"
prompt : "Quarterly business review call"
Time Zone Considerations
Scheduler uses the server’s local time zone . To use a specific time zone:
from apscheduler.triggers.cron import CronTrigger
from datetime import timezone, timedelta
# Example: EST timezone
est = timezone(timedelta( hours =- 5 ))
trigger = CronTrigger(
hour = 9 ,
minute = 0 ,
timezone = est
)
Or set your server’s timezone:
# macOS
sudo systemsetup -settimezone America/New_York
# Linux
sudo timedatectl set-timezone America/New_York
Troubleshooting
Symptoms: Cron time passes but calls don’t triggerSolutions:
Verify schedule is enabled:
agenticai schedule list
# Check "Enabled" column
Check scheduler is running:
agenticai service status
# Should show "Scheduler started"
Verify cron syntax:
# Test at https://crontab.guru/
Check logs for errors:
agenticai service logs -f | grep -i schedule
Manually test the schedule:
agenticai schedule run < nam e > --webhook-url https://your-url.ngrok.io
Reference: src/agenticai/scheduler/scheduler.py:96-115
Symptoms: Scheduler logs show “call initiated” but calls don’t connectSolutions:
Check webhook URL is valid:
curl https://your-webhook-url.com/health
Verify Twilio credentials:
agenticai test-connection
Check phone numbers are valid E.164 format:
# Good
to_number : "+15551234567"
# Bad
to_number : "555-123-4567"
to_number : "15551234567" # Missing +
Review error logs:
tail -50 /tmp/agenticai/agenticai-error.log
Reference: src/agenticai/scheduler/scheduler.py:149-163
Symptoms: Error loading schedules or job not addedSolutions:
Validate cron format:
# Must be 5 parts: minute hour day month day_of_week
"0 9 * * *" # ✓ Valid
"0 9 * *" # ✗ Invalid (missing field)
Check for typos:
# Good
cron : "0 9 * * 1-5"
# Bad
cron : "0 9 * * 1-5 " # Trailing space
cron : "0 9 * * 1-5" # Double space
Use integer ranges correctly:
# Good
cron : "0 9 * * 1-5" # Monday to Friday
cron : "0 9-17 * * *" # 9 AM to 5 PM
# Bad
cron : "0 9 * * Mon-Fri" # Use numbers, not names
Reference: src/agenticai/scheduler/scheduler.py:99-115
Symptoms: schedule list shows no schedulesSolutions:
Verify schedules.yaml exists:
Check YAML syntax:
# Use a YAML validator
python -c "import yaml; yaml.safe_load(open('schedules.yaml'))"
Common YAML errors:
# Good
schedules :
- name : "test"
cron : "0 9 * * *"
# Bad (missing colon)
schedules
- name : "test"
# Bad (wrong indentation)
schedules :
- name : "test"
cron : "0 9 * * *"
Check file permissions:
Reference: src/agenticai/core/config.py (load_schedules)
Next Steps
Service Management Run scheduler as a daemon
Making Calls Manual call triggering
Telegram Integration Monitor scheduled calls
Receiving Calls Handle incoming calls