Scheduling Routines
Run routines automatically on a schedule using cron expressions or simple intervals. The scheduler runs as a background daemon and executes routines at their configured times.
Quick Start
1. Add a schedule field to your .routine.json:
{
"version": 1,
"name": "inbox-triage",
"description": "Automated inbox organization",
"schedule": {
"interval": "30m"
},
"steps": [
{
"id": "run-triage",
"type": "exec",
"cmd": "bash",
"args": ["/path/to/inbox-triage.routine.sh"],
"capture": "text"
}
]
} 2. Start the scheduler:
cli4ai scheduler start 3. Check status:
cli4ai scheduler status Schedule Configuration
Schedules can use cron expressions, simple intervals, or both. If both are specified, the routine runs at whichever comes first.
Cron Expression
Standard 5-field cron syntax with optional timezone:
{
"schedule": {
"cron": "0 9 * * *",
"timezone": "Pacific/Auckland"
}
} Simple Interval
Human-readable intervals: 30s, 5m, 1h, 1d
{
"schedule": {
"interval": "30m"
}
} All Options
{
"schedule": {
"cron": "0 9 * * *",
"interval": "1h",
"timezone": "Pacific/Auckland",
"enabled": true,
"retries": 3,
"retryDelayMs": 60000,
"concurrency": "skip"
}
} | Field | Type | Default | Description |
|---|---|---|---|
cron | string | - | Cron expression (e.g., "0 9 * * *" for 9am daily) |
interval | string | - | Simple interval: "30s", "5m", "1h", "1d" |
timezone | string | system | IANA timezone (e.g., "Pacific/Auckland") |
enabled | boolean | true | Whether the schedule is active |
retries | number | 0 | Retry attempts on failure |
retryDelayMs | number | 60000 | Delay between retries (milliseconds) |
concurrency | string | "skip" | "skip" or "queue" if previous run still executing |
Scheduler Commands
# Start the scheduler daemon
cli4ai scheduler start
# Run in foreground (for debugging)
cli4ai scheduler start --foreground
# Stop the scheduler
cli4ai scheduler stop
# Check status and upcoming runs
cli4ai scheduler status
# View logs
cli4ai scheduler logs
# Follow logs in real-time
cli4ai scheduler logs -f
# View execution history
cli4ai scheduler history
cli4ai scheduler history inbox-triage
# Manually trigger a scheduled routine
cli4ai scheduler run inbox-triage Monitoring
Status
See all scheduled routines and their next run times:
Scheduler: running (PID 12345)
Started: 2025-12-16T20:10:13.659Z
Scheduled routines (2):
✓ inbox-triage
Schedule: interval: 30m
Next run: in 12 minutes
Last run: 2025-12-16T19:40:13.659Z (success)
○ daily-backup
Schedule: cron: 0 2 * * *
Next run: in 6 hours Logs
View scheduler and routine output. Use -f to follow in real-time:
[2025-12-16T20:10:13.659Z] [INFO] Scheduler started
[2025-12-16T20:15:18.090Z] [INFO] Starting routine: inbox-triage
[2025-12-16T20:15:31.256Z] [INFO] [inbox-triage] Starting inbox triage
[2025-12-16T20:15:31.256Z] [INFO] [inbox-triage] Archiving: UptimeRobot alerts
[2025-12-16T20:15:31.257Z] [INFO] [inbox-triage] Archived 3 emails
[2025-12-16T20:15:31.257Z] [INFO] [inbox-triage] AI: Labeling Action (invoice@example.com)
[2025-12-16T20:15:31.258Z] [INFO] Finished routine: inbox-triage (success) History
View past executions with status, duration, and errors:
cli4ai scheduler history inbox-triage --limit 10 Storage
Scheduler state and logs are stored in ~/.cli4ai/scheduler/:
~/.cli4ai/scheduler/
├── scheduler.pid # Daemon PID file
├── state.json # Next runs, last results
├── runs/ # Execution records
│ └── inbox-triage-1702756518090.json
└── logs/
└── scheduler.log # Daemon logs Example: AI-Powered Inbox Triage
This example routine runs every 30 minutes. It uses rule-based matching for known senders, then falls back to Claude for AI classification of unknown emails:
#!/usr/bin/env bash
set -euo pipefail
# inbox-triage.routine.sh with AI classification
LIMIT="${CLI4AI_VAR_LIMIT:-50}"
BASE="is:unread in:inbox"
# Rule-based matching first...
# (archive known senders, label known work emails, etc.)
# Then AI classification for remaining emails
remaining=$(cli4ai run gmail search "$BASE -label:Action -label:Newsletters" "$LIMIT")
echo "$remaining" | jq -c '.[]' | while read -r email; do
from=$(echo "$email" | jq -r '.from')
subject=$(echo "$email" | jq -r '.subject')
snippet=$(echo "$email" | jq -r '.snippet')
id=$(echo "$email" | jq -r '.id')
# Ask Claude to classify
classification=$(echo "Classify this email. Reply with ONE word: ARCHIVE, ACTION, NEWSLETTER, AUTOMATED, or KEEP
From: $from
Subject: $subject
Preview: $snippet" | claude --print | tr -d '[:space:]')
case "$classification" in
ARCHIVE) cli4ai run gmail archive "$id" ;;
ACTION) cli4ai run gmail label "$id" "Action" ;;
NEWSLETTER) cli4ai run gmail label "$id" "Newsletters" ;;
AUTOMATED) cli4ai run gmail label "$id" "Automated" ;;
esac
done
The AI classifies each email as ARCHIVE, ACTION, NEWSLETTER,
AUTOMATED, or KEEP, and applies the appropriate action.
Notes
- JSON only: Schedules are defined in
.routine.jsonfiles. Bash routines can be wrapped in a JSON routine that calls them. - Daemon persistence: The scheduler runs as a background process. Use
launchd(macOS) orsystemd(Linux) to auto-start on boot. - Concurrency: By default, if a routine is still running when its next execution is due, the new run is skipped. Set
"concurrency": "queue"to queue instead. - Retries: Failed routines can automatically retry with configurable delay.
- Timezone: Cron expressions use the system timezone by default. Specify
timezonefor consistent scheduling across environments.
Tip: Start with cli4ai scheduler start --foreground to debug your schedule,
then switch to background mode for production use.