ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-09-06Advanced

With scheduled agents, I now look for runs that never happened before I look for failures

My run ledger showed a 100% success rate while the evening slot had not fired for two weeks. Here is the reconciliation I now run against an expected-fire table, with measured notes on cron expansion, exit codes, and how the ledger itself gets written.

Antigravity364agents140scheduled runs5cron2operations31

Premium Article

I had increased a wallpaper classification batch to twice a day, or so I believed, until the morning the processed counts refused to add up. The ledger looked fine. Every entry said OK. There were no errors anywhere.

The counts were still short. I read the timestamps one by one and finally saw it. Only the morning slot was there. The evening slot had not appeared once in two weeks.

Until then I had been measuring the health of my unattended work by the absence of failures. If nothing failed, I assumed things were fine. But a run that never happens is not recorded as a failure either. Nothing lands in the ledger, and nothing rings. All that remains is the fact that nothing happened.

Since then I have turned the direction of my monitoring around. I did not stop looking at what happened. I added a step before it: counting what was supposed to happen.

Failure alerts cannot reach a run that never started

Written out, the reason is obvious. A failure alert fires only once a process is alive. A run that never started has no process, and therefore nothing to raise the alert.

Antigravity's Remote Control push notifications behave the same way. They arrive when an agent completes a task, or when it needs more input from you — that is, only for runs that began. Those notifications have saved me plenty of evenings, but they are not a channel that can tell you "today it simply did not run."

Counting on my own ledger, three days of records gave me this:

matched runs        : 6
missing runs        : 3
caught by failure-only alerting: 0

The success rate is 6/6, a clean 100%. Read from the side of what was expected, the same ledger is 6/9, or 67%. Same data, different denominator, opposite conclusion.

Count the runs that should have happened, not the ones that succeeded. Putting that sentence at the top of my operations notes was the point where I started trusting my overnight work again.

Hold the expectation as a table, not as an expression

When I started digging, I suspected the cron expression first. It read 30 4,16 * * *. I could not find anything wrong with it. Validating the expression itself led nowhere.

So I stopped reading it and expanded it instead. A short function that opens up the minute and hour fields is enough.

# expect.py - expand a cron expression into the times it should fire
# Day, month and weekday are assumed to be *. For run auditing I only needed
# the minute and hour fields.
from datetime import datetime
 
def parse_field(f, lo, hi):
    """Expand commas, ranges and steps into a set of values."""
    out = set()
    for part in f.split(","):
        if part == "*":
            out |= set(range(lo, hi + 1))
            continue
        if "/" in part:                       # */6 or 0-23/6
            base, step = part.split("/")
            rng = range(lo, hi + 1) if base == "*" else range(
                int(base.split("-")[0]), int(base.split("-")[-1]) + 1)
            out |= set(v for v in rng if (v - min(rng)) % int(step) == 0)
            continue
        if "-" in part:                       # 9-17
            a, b = part.split("-")
            out |= set(range(int(a), int(b) + 1))
            continue
        out.add(int(part))
    return sorted(out)
 
def expected(expr, day):
    """Return the datetimes that should fire on this day, ascending."""
    minute_field, hour_field = expr.split()[0], expr.split()[1]
    minutes = parse_field(minute_field, 0, 59)
    hours = parse_field(hour_field, 0, 23)
    return [day.replace(hour=H, minute=M, second=0, microsecond=0)
            for H in hours for M in minutes]

Running it on my machine:

'30 4,16 * * *'      -> 2 per day  ['04:30', '16:30']
'30 4 * * *'         -> 1 per day  ['04:30']
'30 16 * * *'        -> 1 per day  ['16:30']
'0 */6 * * *'        -> 4 per day  ['00:00', '06:00', '12:00', '18:00']

As an expression, it is two firings. The syntax was never the problem. What actually ran was one. How a given runner treats a multi-slot field turned out to be a separate question from whether the expression is valid — that was the moment it finally landed for me.

Anything I want to run twice a day now lives as two separate jobs. Whatever I gain by keeping the expression compact is worth less than the risk of one half going quiet.

How it is writtenExpected by the expressionFailure modeWhat I do now
30 4,16 * * *2 per dayOne slot can fire alone, silentlyI avoid it
30 4 * * * and 30 16 * * *1 per day eachA stopped slot shows up as a gapI split it this way
0 */6 * * *4 per dayThinning is hard to noticeReconciliation is mandatory

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You will be able to catch runs that never started at all, through a path separate from your failure alerts
You will be able to add expected-versus-actual reconciliation on top of the schedules you already have, without touching them
You will be able to measure the gap between success rate and coverage in your own setup (the same ledger reads 6/6 and 6/9)
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

Agents & Manager2026-07-17
Three Quarters of My Reference Notes Never Reached the Agent: Measuring What head Cuts Away
I fed reference notes to a scheduled agent with cat and head, and the lines that mattered were quietly cut. Here is the measurement, and how I replaced a line count with a section-level contract.
Agents & Manager2026-07-12
What to Delegate to an Antigravity Agent and What to Keep by Hand, After Two Weeks
After two weeks of handing my daily solo-dev tasks to Antigravity agents, a clear line emerged between the work I was glad to delegate and the work I had to pull back. A retrospective with the operational log.
Agents & Manager2026-06-28
Treating Built-in Guide Skills as Design Assets, Not Throwaway Prompts
Antigravity v2.2.1 added built-in Guide skills. Here is a concrete structure and set of judgment calls for running them as version-controlled, shared design assets instead of one-off instructions.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →