Week 2's primitives — retry, dedup, state — exist for one reason: schedule-safety. The mental test for any automation: would this still be safe if it ran at 9am every day, no human watching?
Three sample scripts. For each, find the line that breaks at the hourly mark.
Script A — sends a daily standup email:
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": "team@example.com",
"subject": "daily standup",
"body": "good morning",
})If this fires every hour: 24 emails to your team. Not idempotent — each call creates a new message. The fix: dedup against a seen set keyed by date.
Script B — appends a row with the timestamp:
import datetime
row = [str(datetime.datetime.now()), "event"]
append_row(SHEET_ID, row)The row's content changes every call (timestamp moves). That's actually... fine? Yes — it's append-only logging, so it's meant to grow. But: there's no upper bound. After a year of hourly runs: 8,760 rows. Schedule-safe but unbounded. Add a retention policy.
Script C — reads top 5 unread emails and replies:
emails = fetch_emails(query="is:unread", max_results=5)
for e in emails:
send_reply(e["id"], "Got it.")At 9am: 5 unread → 5 replies → those 5 are now read. At 10am: a new unread arrives → 1 reply. Sane.
But: if the recipient replies back and that thread becomes unread again? Loop: bot → reply → human → reply → bot → ... Add a check that the message wasn't sent by you.
So the audit is more than "is it idempotent"?
Exactly. Idempotency is one axis. Others: bounded growth, infinite loops, time-zone drift, quota cost compounding, partial-failure recovery. Today: practice the audit on three scripts.
Before deploying any script to a schedule, walk through:
Does a re-run with no input change produce the same world state?
send_email daily — duplicates daily.update_event(id=ABC, summary="daily") — same world state.send_email only after dedup-checking the date hasn't been processed.Does the script's footprint grow forever?
Does the script's output trigger its own input?
Is "today" computed the same way at 9am every day?
datetime.now() returns local time; if the runner's TZ flips (DST), "today" shifts an hour.datetime.now(timezone.utc) — UTC has no DST.Does hourly amplification of cost stay reasonable?
If today's run fails, can tomorrow's run catch up?
last_processed_id, processes everything since — missed runs catch up automatically.Before a script ships to a schedule, put on the malicious-input hat: imagine the worst plausible scenario, walk through every line. The audit catches what testing won't.
This week's lessons hand you the tools the audit demands:
Week 2's primitives — retry, dedup, state — exist for one reason: schedule-safety. The mental test for any automation: would this still be safe if it ran at 9am every day, no human watching?
Three sample scripts. For each, find the line that breaks at the hourly mark.
Script A — sends a daily standup email:
toolset.execute_action(Action.GMAIL_SEND_EMAIL, {
"recipient_email": "team@example.com",
"subject": "daily standup",
"body": "good morning",
})If this fires every hour: 24 emails to your team. Not idempotent — each call creates a new message. The fix: dedup against a seen set keyed by date.
Script B — appends a row with the timestamp:
import datetime
row = [str(datetime.datetime.now()), "event"]
append_row(SHEET_ID, row)The row's content changes every call (timestamp moves). That's actually... fine? Yes — it's append-only logging, so it's meant to grow. But: there's no upper bound. After a year of hourly runs: 8,760 rows. Schedule-safe but unbounded. Add a retention policy.
Script C — reads top 5 unread emails and replies:
emails = fetch_emails(query="is:unread", max_results=5)
for e in emails:
send_reply(e["id"], "Got it.")At 9am: 5 unread → 5 replies → those 5 are now read. At 10am: a new unread arrives → 1 reply. Sane.
But: if the recipient replies back and that thread becomes unread again? Loop: bot → reply → human → reply → bot → ... Add a check that the message wasn't sent by you.
So the audit is more than "is it idempotent"?
Exactly. Idempotency is one axis. Others: bounded growth, infinite loops, time-zone drift, quota cost compounding, partial-failure recovery. Today: practice the audit on three scripts.
Before deploying any script to a schedule, walk through:
Does a re-run with no input change produce the same world state?
send_email daily — duplicates daily.update_event(id=ABC, summary="daily") — same world state.send_email only after dedup-checking the date hasn't been processed.Does the script's footprint grow forever?
Does the script's output trigger its own input?
Is "today" computed the same way at 9am every day?
datetime.now() returns local time; if the runner's TZ flips (DST), "today" shifts an hour.datetime.now(timezone.utc) — UTC has no DST.Does hourly amplification of cost stay reasonable?
If today's run fails, can tomorrow's run catch up?
last_processed_id, processes everything since — missed runs catch up automatically.Before a script ships to a schedule, put on the malicious-input hat: imagine the worst plausible scenario, walk through every line. The audit catches what testing won't.
This week's lessons hand you the tools the audit demands:
Create a free account to get started. Paid plans unlock all tracks.