ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-07-12Advanced

Clock Design for Agents: Separating Monotonic and Wall Time to Protect Long Runs

In scheduled and overnight agent runs, computing timeouts, retry backoff, and rate-limit windows from the wall clock breaks in quiet, hard-to-debug ways. This piece walks through a small Clock abstraction that splits monotonic from wall time, with a decision table and testable code.

agents125long-runningmonotonic clocktimeout design2scheduled executionreliability11

Premium Article

One morning I opened the log of an agent I had left running overnight, and my hands stopped. A job that started around 2 a.m. had aborted itself with "timed out after 4 hours and 14 minutes." The actual work should have taken about ten minutes. After some digging, it finally clicked: the machine had slept partway through, and on wake the code treated the difference in wall-clock time as "elapsed time." The work was healthy. Only the way I read the clock was wrong.

As an indie developer building apps on my own, I hand a batch of maintenance chores to a scheduled overnight run, and this kind of clock-induced misbehavior is hard to avoid. The longer you entrust to an agent, the more that an unglamorous decision — how you measure time — quietly determines whether you can sleep while it works. This piece lays out a small design that deliberately separates a monotonic clock from a wall clock, with working code and a decision table.

Why elapsed time on the wall clock breaks

Let's start with the pattern most code adopts without thinking about it.

const start = Date.now();
await doWork();
const elapsedMs = Date.now() - start;
if (elapsedMs > 30_000) {
  throw new Error("work exceeded 30 seconds");
}

It looks perfectly reasonable, and for short work it rarely causes trouble. But Date.now() returns wall-clock time — a human-facing "what time is it right now." The wall clock moves for reasons that have nothing to do with your program.

Here are the situations where that difference drifts away from the real duration.

EventEffect on the wall clockWhat happens to elapsed-time math
NTP time correctionJumps forward or back by hundreds of ms to secondsElapsed goes negative, or spikes
VM or laptop suspend/resumeLeaps forward by the time it was pausedHealthy work is misjudged as a timeout
Daylight saving transitionShifts by one hourOvernight jobs land off by an hour
Manual or sync time changeJumps arbitrarilyRetry intervals and rate-limit windows break

The nastiest case is when time moves backward. If NTP pulls the wall clock into the past, Date.now() - start becomes negative, and elapsedMs > 30_000 never becomes true. The timeout never fires, and the agent waits forever. Conversely, right after resuming from suspend the difference balloons, and a job that hasn't finished anything gets cut off. Either way, the log only ever says "timeout" or "hung," and you burn hours before reaching the real cause.

The monotonic clock as an alternative

At the heart of the problem is reusing a clock meant for "displaying the current time" to "measure how long something took." For durations, the right tool is a monotonic clock.

A monotonic clock is guaranteed never to run backward. It advances only forward, whether NTP corrects the system time or DST flips. In Node.js that is process.hrtime.bigint(); in browsers and Deno it is performance.now(). What it returns is not a "when" but a relative "how far from some origin." You can't read it as a human timestamp, but take the difference of two readings and you get an accurate duration.

const start = process.hrtime.bigint();
await doWork();
const elapsedMs = Number(process.hrtime.bigint() - start) / 1_000_000;
if (elapsedMs > 30_000) {
  throw new Error("work exceeded 30 seconds");
}

The visible change is tiny, but the meaning shifts a great deal. This elapsedMs reflects the real time that actually passed, no matter how the machine slept or how NTP thrashed. The catch is that a monotonic value is relative to an origin, so it can't be used across processes or written to a log as a human-readable time. That is exactly where a division of responsibility is needed.

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
Why timeouts, backoff, and rate-limit windows silently break when measured on the wall clock during VM suspend or NTP correction
A small Clock abstraction that routes durations to a monotonic clock and timestamps to the wall clock
A decision table for which clock to use per purpose, plus how to test timing logic deterministically
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 $10 for lifetime access
View Membership →

Related Articles

Agents & Manager2026-06-22
Your Antigravity Custom Tools Don't Break by Design — They Break on Re-execution: Field Notes on Idempotency and Error Contracts
Once you add a custom tool to an Antigravity agent, the real production problem is re-execution and duplicated side effects. Here are the idempotency keys, error contracts, health gates, and tool-sprawl checks that actually held up in practice.
Agents & Manager2026-06-20
When a Timed-Out Unattended Agent Leaves a Half-Written File Behind
When a scheduled agent gets killed on timeout, it can leave a half-written file that silently poisons the next stage. Here is the atomic write, stale-temp cleanup, and post-write content assertion I use to keep unattended pipelines from breaking.
Agents & Manager2026-06-13
When a Scheduled Agent Runs Twice — Designing for Idempotency Against Overlap and Retry
A scheduled agent can do the same work twice when the next run triggers before the last one finishes. Here is a design with an overlap lock and an idempotency guard that survives mid-run failures, drawn from a double-publish incident I ran into in production.
📚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 →