The code route: use AI once, then run it free forever
Have a model write the script in an afternoon. Hand it to a scheduler that costs nothing. No task limits, no per-run cost, no drift — and it still works in three years.
The other three articles in this set cover scheduling the assistant itself. This one covers the alternative: using the assistant once, to produce something that then runs without it.
The distinction isn't about avoiding AI. You still use a model — heavily, in the writing. You just stop paying it rent.
Why this is the cheap option at any real scale
A model call has a marginal cost. A script does not. That difference is invisible at one run a day and decisive everywhere else:
| Frequency | Runs/year | Assistant-in-the-loop | Script on a scheduler |
|---|---|---|---|
| Daily | 365 | Pennies — ignore it | $0 |
| Hourly | 8,760 | Single to double digits | $0 |
| Every 5 min | 105,000 | Hundreds | $0 |
| 200 sources, hourly | 1,752,000 | Not offered at any price | $0 |
The last row is the important one, and it isn't about money. No assistant sells 1.7 million scheduled runs — the products cap at three to fifteen active tasks. Past a certain ambition the question stops being "which is cheaper" and becomes "which one can do this".
Meanwhile the right-hand column never moves. The thinking happened once, when you wrote the script. Every run after that is free because there's nothing left to think about.
Use the model properly, then let it go
The workflow that makes this fast:
Describe the job precisely. Not "write me a scraper" — state the URL, the exact fields, where output goes, what should happen on failure, and how it should remember what it saw last time.
Ask for the whole file. Including imports, error handling and the schedule config. Fragments cost you more time than they save.
Run it immediately. The first version will have something wrong. Paste the error back and iterate — this loop is where the model genuinely shines and it's usually two or three rounds.
Ask what breaks it. "What are the three most likely ways this fails in production?" is the highest-value question in the whole process, and almost nobody asks it.
Then stop. Once it works, the model's job is finished. Everything after this is free.
GitHub Actions — free, versioned, nothing to maintain
The default for scheduled work. Public repositories get standard runners free; private ones draw on an account allowance — 2,000 minutes a month on Free, 3,000 on Pro. A two-minute daily job uses about sixty of those.
name: watcher
on:
schedule:
- cron: "0 7 * * *" # UTC
workflow_dispatch: # run by hand from the Actions tab
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 watch.py
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
Three traps, all of which catch everyone once
Schedules are UTC. Your 7am job drifts an hour when the clocks change. If local time matters, schedule both and exit early in the wrong one.
Runs are queued and can be late. Shared infrastructure, sometimes several minutes behind. Fine for a digest, not for anything time-critical.
The filesystem is fresh every run. This is the one that silently ruins change-detecting bots. Your SQLite file is gone, so every run looks like the first run and everything looks new. Either commit the database back at the end of the job, or use the Actions cache:
- uses: actions/cache@v4
with:
path: watcher.db
key: watcher-db-${{ github.run_id }}
restore-keys: watcher-db-
cron — when you need state to survive
On a Raspberry Pi or any machine that stays on. Free, yours, and the disk persists, which makes it strictly better than Actions for anything keeping a history.
0 * * * * cd /home/pi/bot && /usr/bin/python3 watch.py >> bot.log 2>&1
⚠ Cron does not inherit your shell environment. Your API keys won't be there and the job fails silently. Set the variables in the crontab itself or source them in a wrapper — and always redirect output to a log, or you'll never learn it broke.
On a modern Linux box, systemd timers are the better version: real logs through journalctl, automatic retry, and Persistent=true to run a missed job when the machine comes back rather than skipping it.
The hybrid, which is what you usually want
This isn't an argument for never calling a model on a schedule. It's an argument about where in the run that call sits:
code → fetch all 200 pages free
code → compare to last time free — 198 unchanged, discarded
model → judge the 2 that moved 2 calls, not 200
code → notify, log, store free
That structure keeps the intelligence and drops 99% of the cost, because the cheap deterministic gate runs first. It's the same pattern as the door camera on this site, where numeric frame comparison eliminates almost every frame before anything reaches a vision model.
What you give up
Honesty requires this section. Code needs maintaining — when a site redesigns, your selector breaks and an assistant reading the page would have coped. Setup is an afternoon rather than a sentence. There's a floor of technical skill. And it only does what you coded, so genuine novelty goes unhandled.
If the task runs once a day and needs judgment every time, those trade-offs aren't worth making. Use the assistant.
Where to start
The change watcher is this entire article as a working build: about sixty lines, real state in SQLite, free to run forever. The hosting guide covers picking a home for it, and the overview has the full comparison if you're still deciding.
One new build sheet a week
Guides like this one, plus a new build sheet every Thursday. Written by a person who built it.