Error Tracking and Uptime Monitoring Expose Different Cron Heartbeat Evidence A developer argues that error tracking and uptime monitoring cannot prove a scheduled AI agent job actually ran, since an agent that never starts throws no exception and leaves no latency or cost record. The recommended approach is an externally evaluated completion heartbeat: give each scheduled occurrence a stable ID, record completion only after durable output and measurements are committed, and page on a missing deadline rather than a quiet error stream. The developer notes that retries and monitoring must agree on the identity of a single occurrence, and that a start ping alone only proves dispatch, not completion. Error tracking and uptime monitoring cannot prove that a media cron job sent its completion heartbeat. An AI agent that never starts produces no exception, no latency sample, and no cost record, so scheduled work needs an external deadline check. TL;DR: use error tracking for failures observed by running code, uptime monitoring for reachable services, and an externally evaluated heartbeat for scheduled work that might never run. Give every scheduled agent loop a stable occurrence ID, record completion only after its durable output and measurements are committed, and page on a missing deadline rather than on a quiet error stream. After pages for both missed jobs and duplicate deliveries, I no longer treat a quiet error stream as evidence of health. The runbook lesson is the same for both: retries and monitoring must agree on the identity of one scheduled occurrence. An error event proves that some code saw a failure. A completion heartbeat proves that expected work reached a defined terminal state. Neither claim contains the other. Picture a publisher that runs an agent after each editorial batch. The agent classifies stories, records model latency and cost, then writes an enrichment result. Its web service can answer an uptime probe throughout the night. Its error tracker can remain empty too. If the scheduler never dispatches the 02:00 occurrence, there is no process in which to throw an exception. That is a silent failure, not a successful zero-work interval. An empty latency chart could mean that no calls were due, telemetry stopped, the queue stalled, or the run never began. Likewise, a zero cost total says nothing about scheduling health. Metrics from completed calls describe calls that happened; they cannot prove an expected call existed. Silence proves nothing. The invariant is blunt: absence must be judged by a clock outside the job. If the same worker both performs the task and decides whether its missing completion is alarming, one failure can remove the work and its witness. I used to begin these pages by looking for an exception. A missed job taught me to start one step earlier: find the expected occurrence, then ask which transition is absent. No occurrence record points toward scheduling. A started record without completed points toward the worker or its dependencies. A completed record with no accepted output is a transaction problem. Those are different response paths, and forcing them all through an error dashboard wastes the first minutes of an incident. This is also why a start ping is insufficient. It proves dispatch. For a loop that fans out across a media batch, completion should mean that the accepted output and its latency and cost record are durable. If partial results are valid, define that terminal state before the incident. A green ping cannot invent the policy afterward. The three signal types overlap on a dashboard, but their evidence differs. | Signal | What it establishes | What it can miss | Useful first response | |---|---|---|---| | Error tracking | Executed code observed a crash, thrown exception, or explicitly captured error | A process that never started; a valid but wrong early return | Inspect stack, context, and release | | Uptime monitoring | A target responded under the probe's conditions | A stopped scheduler or worker while the API remains reachable | Check the service and network path | | Completion heartbeat | A named occurrence checked in before its deadline | Semantically bad output from a completed run | Inspect scheduler, queue, worker, and occurrence ID | Signal quality depends on refusing to overload those claims. Sending an error event for a missed deadline fabricates an application failure that no application observed. Treating a successful health endpoint as proof of batch completion hides independent failure domains. Calling every late run immediately, meanwhile, creates enough noise that operators stop trusting the page. That page gets muted. The grace period should cover normal execution time, queue delay, and the retry policy. Do not copy the five-minute schedule into a five-minute alert threshold by reflex. Use the observed distribution of successful runs, add the operational margin your response policy requires, and revisit it when the workload or model route changes. There is no universal duration in this advice. The occurrence identity should be deterministic: job name plus scheduled time is often enough. A retry that generates a fresh random ID looks like new work, which can produce both duplicate publication and duplicate telemetry. The completion write should accept the same result twice, yet reject a second result with a different digest for the same occurrence. This isn't bookkeeping. It is the difference between one delayed edition and two published editions. The capture path below sends an error that running code has actually observed. The JSON body comes from INFRAI ERROR CAPTURE JSON because the live discovery schema, rather than an article, should define its fields. The client uses the verified route, an environment credential, an explicit method, response-body errors, and bounded exponential backoff that honors Retry-After on HTTP 429. It does not send a capture for a missing run; the external heartbeat evaluator owns that case. package main import "bytes" "fmt" "io" "net/http" "os" "strconv" "strings" "time" func delay retryAfter string, attempt int time.Duration { if seconds, err := strconv.Atoi retryAfter ; err == nil && seconds = 0 { return time.Duration seconds time.Second } return time.Duration 1<