Field Guide · 07

Automations That Fire

Scheduled agents are where Zo stops being a chatbot and starts being staff. They are also where the silent failures live: the brief that never arrived, the run that fired at the wrong hour, the polling loop that quietly ate a month of credits. The mechanics of creating an automation are in the Zo docs (https://docs.zocomputer.com/automations.md). This guide is the judgment: how to design runs that work, and how to debug the ones that do not.

First, the mental model

An automation is not your conversation continuing on a schedule. Each run is a fresh agent that wakes up with the instructions you wrote, and nothing else. It does not remember last run. It does not remember the chat where you created it. It cannot ask you a clarifying question, because you are asleep; that is the point.

Every design rule below falls out of those three facts: fresh memory, fixed instructions, nobody to ask.

Timezones: the number one cause of "it fired at the wrong time"

Schedules are commonly expressed as recurrence rules (rrule), and the classic trap is the hour field. Do not assume the hour you set is interpreted in your local time, and do not assume it is UTC either. Platforms differ, and getting it backwards moves your 7am brief to noon, or to 2am. From our own logs: an hour value that we assumed meant UTC was actually interpreted as local time. The fix took one minute; noticing took days, because a brief that arrives at the wrong hour still arrives.

The protocol:

  1. When creating an automation conversationally, say the timezone in words: "7am US Eastern, my local time," not just "7am."
  2. Verify empirically. Schedule the first run a few minutes out, watch it fire, then set the real schedule. One test run beats any amount of reasoning about what the platform probably does.
  3. Remember daylight saving. A UTC-pinned schedule drifts an hour against your wall clock twice a year. If the run must track your mornings, make sure it is pinned to your zone, not to UTC.

Rrule patterns that earn their keep

A few shapes cover nearly everything:

  • Daily at a time: FREQ=DAILY;BYHOUR=7;BYMINUTE=0
  • Weekdays only: FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=7
  • Weekly review: FREQ=WEEKLY;BYDAY=SU;BYHOUR=17
  • Monthly on a date: FREQ=MONTHLY;BYMONTHDAY=1;BYHOUR=9

Two non-obvious tips from production use:

  • For a one-off reminder weeks out, prefer a monthly rule over a daily-with-count contraption. "Remind me on the 28th" maps cleanly to FREQ=MONTHLY;BYMONTHDAY=28 with the agent told to disable itself after firing. Long chains of daily logic to approximate a distant date are fragile.
  • Mind the edge dates. BYMONTHDAY=31 simply never fires in a 30-day month. Use BYMONTHDAY=-1 semantics for "last day" if supported, or pick day 28.
  • Be explicit about every setting you care about (model, schedule, timezone) rather than trusting defaults. Defaults change, and an automation built on assumed defaults fails in ways that look random.

Cost: every run is a real agent run

A scheduled agent burns credits every time it wakes, whether or not it finds anything to do. The math sneaks up on people:

  • Daily = ~30 runs a month. Fine for anything useful.
  • Hourly = ~720 runs a month. This needs a reason.
  • Every 5 minutes = ~8,600 runs a month. This is almost always a design error.

The smell to watch for is polling: "check every N minutes whether something happened." Most checks find nothing, and you pay full price to learn nothing. Before scheduling anything more often than hourly, ask: can the event push instead (a webhook into a service)? Can the check run less often with a wider lookback? Does anyone actually act on this faster than daily? Honest answers kill most high-frequency schedules. The full cost picture lives in Cost and Usage.

Also keep the instructions lean. The prompt runs every time; a bloated prompt is a per-run tax. And give the agent an early exit: "if there is nothing new, stop immediately" keeps the nothing-happened runs cheap.

Design rules for runs that survive contact with reality

State lives in files, not in memory. The run cannot remember what it processed last time, so it must read and write that fact somewhere durable: "read state.json for the last-processed timestamp; process newer items; write the new timestamp back." No state file means double-processing or gaps, guaranteed eventually.

Make runs idempotent. Design every run so that running twice is harmless: check before creating, skip what is already done, overwrite rather than append where repeats would hurt. Schedulers misfire, and you will also re-run things manually while debugging. Idempotent runs make all of that boring instead of damaging.

Leave a trail. Have every run append one line to a log file: timestamp, what it did, or "nothing to do." This single habit converts "did it even run?" from a mystery into a thirty-second lookup, and it is the first thing you will want when debugging.

Write the instructions for a stranger. The run gets no follow-up questions, so ambiguity becomes improvisation. Name exact paths, exact recipients, exact formats, and what to do in each likely situation, including the empty one and the error one: "if the source is unreachable, write the error to the log and stop; do not send a half-brief."

Decide the failure behavior on purpose. Should a failed run alert you, retry, or skip silently and let the next run cover it? For anything that matters, make failure loud, and remember the watchdog principle: a monitor that lives inside the thing it monitors dies with it. Our production systems pair internal schedules with an independent external check precisely because of this.

Debugging: "my automation didn't fire"

Work the list in order; it is sorted by actual frequency.

  1. It fired at a different time than you think. Timezone, again. Check the run history or your log file for evidence of when it actually ran before concluding it never did.
  2. It is disabled, expired, or was never saved. List your automations and confirm it exists, is enabled, and shows the schedule you expect. Conversational setup can fail politely: you said "schedule it," the conversation moved on, and nothing got created.
  3. The schedule means something other than what you intended. Read the rrule literally and check the next-fire time if the platform shows one. An UNTIL in the past, a count that ran out, or a BYDAY you did not mean are all common.
  4. It ran and failed. Look at the run's output or your log trail. A run that errors mid-way often looks identical to a run that never happened, unless you left a trail (see above).
  5. It ran fine and the delivery failed. The brief was generated and the send step broke, or went to the wrong place. Check the last step, not just the first.
  6. Still nothing. Reproduce small: create a copy scheduled two minutes from now with a trivial action ("append the time to test.log"). If the copy fires, the problem is your automation's content. If it does not, the problem is platform-side: check the docs and ask in #help-and-support.

The pre-flight checklist

Before walking away from a new automation:

  1. Timezone stated explicitly, and the next-fire time confirmed?
  2. First run tested by scheduling it minutes out and watching it?
  3. Frequency honest, no polling that should be a push?
  4. State in a file, runs idempotent, log line per run?
  5. Instructions complete enough for a stranger, including the empty case and the error case?
  6. Failure mode chosen: who finds out, and how fast?

Six yeses and your automation is the boring kind: the kind that just fires, every time, and you forget it exists until the day you realize it has quietly saved you a hundred mornings.

← All guidesNext: Cost and Usage

Classified Holdings LLC · The AI-first agency