ailiteracynepal 🇳🇵
Text size

Chapter 02 · Section III · 10 min read

2.3 Scheduled loops: runs on the clock

The third flavour of trigger — the clock. The loop fires at a time you picked, whether or not you're at your laptop. First real step into unattended work.

Scheduled loops fire on the clock. Every morning at 6:00, every Monday at 9:00, every fifteen minutes on the hour. You are not in the chair. Your laptop may be closed. The loop runs anyway because the trigger lives somewhere else — a cron server, a scheduled workflow, a hosted job — that doesn’t need you awake.

Why the trigger has to leave your machine

A live loop needs a session on your laptop. A run-until-done loop needs one command from you to start. A scheduled loop needs neither — which is only possible if the thing that starts it isn’t your laptop.

Three common places the clock lives:

  • A cron server (or systemd timer). A machine you own, always on, running a scheduler that fires jobs at set times.
  • A cloud scheduler. A hosted service (Cloud Scheduler, EventBridge, GitHub Actions cron) that fires a webhook or triggers a workflow at set times.
  • A scheduling feature inside an agent product. The agent tool has a “run this daily at 6am” button and handles the machinery for you.

The choice matters mostly for one thing: what happens if the scheduler is down? A cron server on your laptop misses the 6am run if the laptop is off. A cloud scheduler doesn’t. Pick the one whose failure mode you can live with.

What changes when you leave

Everything you could paper over in live and run-until-done becomes real:

  • Nobody catches the wrong output. If the loop emails a wrong summary at 6am, the first person to notice is the customer at 8am.
  • Nobody notices when it doesn’t run. Silent failure — the scheduler didn’t fire, the job crashed, the model returned an error — will happen and you need to know.
  • Nobody stops the runaway. If the loop hangs, retries forever, or wastes tokens, there’s no Ctrl-C. The budget (from 2.2) is your only stop.
  • Nobody rescues the sandbox. If a cycle corrupts state, tomorrow’s cycle inherits the corruption. Chapter 3.1 (isolation) and Chapter 4 (memory) start mattering a lot.

None of this makes scheduled loops bad — most useful loops are scheduled. It just means the moment you cross from run-until-done to scheduled, you have to do the work you got away with skipping before.

Picking the schedule

The right cadence is usually one of a small number:

  • Hourly / every few minutes. For work tied to fast-changing external state (a monitoring alert, a fast-moving inbox).
  • Daily. For summaries, reports, digests, batch triage. The most common cadence.
  • Weekly. For roll-ups, reviews, longer-cycle analyses.
  • Monthly. For billing-adjacent work, quarterly-prep work, planning cycles.

Two anti-patterns:

  • Every minute “just in case.” If the underlying state doesn’t change that often, you’re paying 60× more than you need to and drowning your logs. Match the cadence to the rate the input actually changes.
  • Once a year. By the time the loop runs, nobody remembers if it works. Annual loops rot silently. Prefer monthly with a manual “the annual one” pass.

The idempotency question

Every scheduled loop must be safe to run twice. Networks glitch, schedulers double-fire, retries happen. If a second run of the same cycle produces a different outcome — sends a duplicate email, files a duplicate ticket, charges a duplicate invoice — that’s a bug, not “just unlucky.”

Two ways to make a cycle safe to re-run:

  • Check before you act. Before sending, ask “has today’s summary already been sent?” If yes, skip.
  • Use a deterministic key. When creating a record, give it an ID derived from the day + task, so a duplicate creation is a no-op or an update-in-place.

Idempotency is not glamorous work. It is what separates a loop you can leave running from one you can’t.

Reading the log the morning after

The morning-review habit: every scheduled loop should produce a single, glanceable “what did I do last night” summary that lands somewhere you’ll actually see (Slack, an email, a channel, a doc). Two rules:

  • Silent success is fine — but only if you trust silent success. Most scheduled loops should post something every run, even just “ran cleanly; 12 tickets processed; nothing needed human review.”
  • Loud on failure. If a cycle errored, the message should be impossible to miss, name the failing step, and (ideally) link to the log.

Check your understanding

Quick check

You promote a live loop that "summarises overnight tickets" into a scheduled loop that fires every morning at 6am. Which single change is MOST important to add before you leave it running?