How to Build an AI Agent That Runs on a Schedule (Cron, GitHub Actions, Cloudflare)
Updated 2026-10-15
Many useful agents aren’t chatbots at all: a morning inbox digest, an hourly price checker, a weekly report. This guide shows three ways to run an agent on a schedule, from simplest to most serverless.
Prerequisites
- An agent script that runs end to end from the command line (e.g. from Build your first AI agent in Python)
- An LLM API key
Make the agent schedule-friendly first
A scheduled agent runs with no one watching, so:
- Hard-cap steps and tokens (
max_steps,max_tokens). - Make it idempotent: record what was already processed (a file, a DB row) so a re-run doesn’t double-send.
- Send the result somewhere (email, Slack, a file) and log failures loudly.
# daily_agent.py
import datetime, json, pathlib
from agent import run_agent # your agent loop
state = pathlib.Path("state.json")
seen = json.loads(state.read_text()) if state.exists() else {}
today = datetime.date.today().isoformat()
if seen.get("last_run") == today:
raise SystemExit("already ran today")
summary = run_agent("Summarize today's top 5 Hacker News stories for a developer.", max_steps=6)
print(summary) # or post to Slack / email
state.write_text(json.dumps({"last_run": today}))
Option 1: cron on a server or Mac/Linux machine
crontab -e
# every day at 07:30 server time
30 7 * * * cd /home/me/agent && /home/me/agent/.venv/bin/python daily_agent.py >> agent.log 2>&1
Cron does not load your shell profile. Use absolute paths and put API keys in an env file the script loads (e.g. with python-dotenv).
Option 2: GitHub Actions (free, no server)
# .github/workflows/agent.yml
name: daily-agent
on:
schedule:
- cron: "30 14 * * *" # 14:30 UTC daily
workflow_dispatch: {} # lets you run it by hand
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r requirements.txt
- run: python daily_agent.py
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Add the key under Settings → Secrets and variables → Actions. Schedules are in UTC, can start several minutes late at busy times, and are disabled automatically in public repos with no activity for 60 days.
Option 3: Cloudflare Workers Cron Triggers (serverless, JavaScript)
# wrangler.toml
name = "scheduled-agent"
main = "src/index.js"
compatibility_date = "2026-10-01"
[triggers]
crons = ["30 14 * * *"]
// src/index.js
export default {
async scheduled(controller, env, ctx) {
const r = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { "Authorization": `Bearer ${env.OPENAI_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
model: "gpt-4.1-mini",
messages: [{ role: "user", content: "Write a one-line motivational note for a developer." }],
}),
});
const data = await r.json();
console.log(data.choices[0].message.content); // or POST to a Slack webhook
},
};
npx wrangler secret put OPENAI_API_KEY
npx wrangler deploy
npx wrangler dev --test-scheduled # then: curl "http://localhost:8787/__scheduled?cron=30+14+*+*+*"
Workers have CPU-time limits; network waiting on the LLM doesn’t count toward CPU time, but long multi-step agents may suit a Workflow or a queue better.
Common errors
Cron job does nothing: check agent.log; usually python: command not found or a missing env var. Use absolute paths.
GitHub Action never fires: the workflow file must be on the default branch, and scheduled runs pause after 60 days of repo inactivity.
Runs at the wrong hour: GitHub Actions and Cloudflare cron use UTC.
Agent sends the same report twice: add the idempotency check shown above.