Skip to content
AI Agent How-To

Home › Build an agent that does X

How to Build an AI Agent That Runs on a Schedule (Cron, GitHub Actions, Cloudflare)

Intermediate ~6 min read Last tested October 2026

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

Make the agent schedule-friendly first

A scheduled agent runs with no one watching, so:

  1. Hard-cap steps and tokens (max_steps, max_tokens).
  2. Make it idempotent: record what was already processed (a file, a DB row) so a re-run doesn’t double-send.
  3. 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.