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.json files. Bash routines can be wrapped in a JSON routine that calls them.
  • Daemon persistence: The scheduler runs as a background process. Use launchd (macOS) or systemd (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 timezone for consistent scheduling across environments.

Tip: Start with cli4ai scheduler start --foreground to debug your schedule, then switch to background mode for production use.