[KongchangAI]
· 2 min read· 1,476 words

Making Polling Agents Truly Run 24/7: From In-Session Timers to External Schedulers

Making Polling Agents Truly Run 24/7: From In-Session Timers to External Schedulers

In-session timers are just prototypes — real 24/7 agents need external schedulers, timeouts, and independent heartbeat monitoring.

A developer discovered his "always-on" agent only ran while his terminal was open — the scheduling logic lived in a `while True + sleep` loop that died with the session. This article uses that as a springboard to cover four core engineering constraints for reliable long-running agents: replacing in-session timers with external schedulers (cron, systemd timer, Task Scheduler, or managed workers); setting hard timeouts on every external call; using exponential backoff on failures; and detecting silent failures through `last_success` timestamps paired with an independent dead man's switch heartbeat monitor.

A Common Self-Deception: Your Agent Only "Lives" While Your Terminal Is Open

A Reddit developer recently confessed to a painful mistake: he built a polling agent that checked a marketplace for new tasks and claimed it ran "24/7 around the clock" — but the scheduling logic was actually embedded inside his own coding session. The moment he closed the session, the agent went "deaf" and stopped pulling any new data.

This might seem like a beginner mistake, but it's remarkably common. The first time most people build an automated agent, they write the scheduling logic as an in-session timer — something like a while True + sleep loop in a script, or a Python process running in a notebook. It works great during development and gives the impression that something is "continuously running." But the moment you close the terminal, shut the laptop, or the system goes to sleep, the entire polling loop silently dies.

Real "long-running" operation should never depend on an interactive session staying alive. After recognizing the problem, this developer outlined a migration plan that's worth studying for anyone building agent automation.

Original Reddit thread discussion

From "In-Session Timers" to "External Schedulers"

The core change the original poster proposed was to hand scheduling authority from inside the session to an OS-level external scheduler. He personally uses Windows Task Scheduler — the system triggers a polling script on schedule rather than relying on a timer living inside a persistent session.

The key distinction is:

  • In-session timer: Process alive = task alive. When the process dies, everything stops — and often nobody knows it died.
  • External scheduler: Each polling run is an independent, short-lived task. The system guarantees it fires on time; even if the previous run crashed, the next trigger is unaffected.

In practice, each platform has its own mature options:

Common External Scheduling Approaches

  • Linux/macOS: cron or systemd timer. cron is simple and direct; systemd timer provides better logging, dependency management, and failure-restart capabilities.
  • Windows: Task Scheduler (the original poster's choice) — configurable trigger conditions, failure retry, and wake-from-sleep strategies.
  • Small persistent daemon: If polling frequency is very high (sub-second), a long-running process managed by systemd/supervisor may be more appropriate — but configure automatic restarts.
  • Managed Worker / Serverless: Cloud-based scheduled tasks, Cloudflare Workers Cron, AWS EventBridge + Lambda, etc. Completely eliminate dependency on local machines — it doesn't matter whether your laptop is on or off.

The key decision is: should this agent depend on your personal device at all? If the answer is "no," then a local scheduler is only a transitional solution — the end goal is migrating to a managed environment.

systemd timer is the modern Linux replacement for cron and deserves special mention. It consists of two files: a .timer unit that defines the trigger schedule, and a .service unit that defines what to execute. Compared to cron, its advantages include: execution logs are unified in journald and queryable via journalctl; it supports an OnFailure= directive to trigger another unit on task failure (e.g., sending an alert email); and Persistent=true can be set to ensure that scheduled tasks missed during a system shutdown are run once on the next boot. For production agents that need observability and error handling, systemd timer is generally superior to cron. Serverless scheduled triggers (e.g., AWS EventBridge Scheduler + Lambda, Google Cloud Scheduler + Cloud Run) represent the other extreme: fully managed, billed per execution, with built-in retry policies — though the trade-off is cold-start latency and more complex debugging, making them best suited for low-frequency but high-reliability scenarios.

Making Every Invocation "Controlled and Recoverable"

External scheduling alone isn't enough. The additional points the original poster raised are what actually transform a fragile script into a reliable service.

Hard Timeout

Every external call needs a strict timeout. As the original post makes clear: a hung request must not freeze the entire workflow. Network requests without timeouts are the most insidious killer in long-running tasks — they don't throw errors, they don't exit, they just hang there indefinitely, making you think the program is still running normally.

Error Backoff

When errors occur, don't immediately retry in a frantic loop — use a backoff strategy (such as exponential backoff). Hammering an API with high-frequency retries can trigger rate limiting at best and get you banned at worst. Proper backoff protects the remote service and allows your agent to gracefully wait out a temporary outage.

Exponential backoff standard practice: wait 1 second after the first failure, 2 seconds after the second, 4 seconds after the third, doubling each time up to a cap (e.g., 64 seconds or 5 minutes). To prevent multiple clients from retrying in sync and causing a "thundering herd," random jitter is typically added — a random offset applied on top of the backoff interval. In the Python ecosystem, the tenacity library provides ready-to-use backoff retry decorators. For HTTP scenarios, also watch for the Retry-After response header — some APIs return this alongside a 429 (Too Many Requests) response to explicitly tell clients how long to wait, and you should honor that value rather than calculating your own backoff.

The Most Overlooked Part: How Do You Know It "Quietly Stopped"?

The most valuable insight in the original post is the author's vigilance against "silent failure."

His approach: maintain a last_success timestamp, and if that timestamp becomes stale, trigger an alert. Even if the agent dies silently somewhere in the pipeline, you'll be notified — rather than discovering days later that "somehow no new tasks were captured."

This "heartbeat + staleness detection" pattern is critical. The most dangerous state in an automated system isn't a crash with an error — it's looking like it's running while actually doing nothing. Concrete monitoring strategies include:

  • Dead man's switch: The agent pings a monitoring service (e.g., Healthchecks.io, Cronitor) on every successful execution. If the expected heartbeat doesn't arrive on time, the monitoring service proactively alerts you.
  • Write last_success to disk or database + periodic validation: A separate, simpler check job determines whether the timestamp is stale.
  • Log execution results + alert on exceptions: Push each polling result and any errors to a logging system or IM platform (Slack, Feishu, Telegram), with direct notifications on anomalies.

Note that monitoring itself must be independent of the agent being monitored. If the alerting logic and polling logic run in the same process — one that can die at any moment — then when it dies, no one sends an alert. That's exactly the same trap the original poster fell into in the first place.

Dead man's switch as a concept originates from railway safety systems: the operator must continuously hold down a control handle, and if they release it (i.e., become incapacitated), the train stops automatically. Translated to software monitoring, the logic inverts: the system actively sends a heartbeat signal during normal operation; if the monitoring platform doesn't receive a signal within the preset time window, it treats it as an anomaly and triggers an alert. Healthchecks.io is an open-source/hosted service designed exactly for this use case — each monitored job gets a unique URL, and the agent sends an HTTP GET request to that URL after each successful execution to record the heartbeat. If no request arrives within the configured period, the platform sends alerts via email, Slack, PagerDuty, and other channels. The critical advantage of this pattern is complete decoupling between monitoring infrastructure and the monitored process — even if the monitored machine goes completely offline, the monitoring side can still detect the missing heartbeat.

A Reusable Reliable Agent Checklist

Drawing from the original poster's reflections and common community practice, a polling agent that can truly run long-term should have:

  1. Scheduling decoupled from business logic: Use an external scheduler (cron / systemd timer / Task Scheduler / managed Worker) to trigger execution — not an in-session timer.
  2. Short-lived tasks: Each execution starts independently and exits independently, avoiding state contamination and memory leaks from long-running processes.
  3. Hard timeouts: Every external call has a timeout ceiling — no infinite hangs allowed.
  4. Error backoff: Exponential backoff retry on failure — protecting the API and protecting yourself.
  5. Heartbeat and staleness alerts: Record last_success, independently monitor its freshness — silent failures must be detectable.
  6. Consider moving off local devices: If the business truly requires 24/7 availability, eventually migrate to a managed environment. Don't tie reliability to a laptop that might close its lid and go to sleep.

This developer's candid reflection reminded many people: "24/7" isn't just a slogan — it's a set of specific engineering constraints. An in-session timer is just a development prototype. To make an agent work reliably around the clock, you need to nail down scheduling, timeouts, backoff, and monitoring — every single one of them.

#polling agent#24/7 agent scheduling#cron systemd timer#dead man's switch monitoring#exponential backoff#Task Scheduler automation#silent failure detection
Share:

Related articles