{"slug": "one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent", "title": "One heartbeat, not 84 cron jobs: the scheduler at the core of a self-hosted agent", "summary": "A developer behind OpenAmer, an open-source Apache-2.0 desktop AI agent that runs locally on Windows, replaced 84 separate cron and Task Scheduler jobs with a single in-process \"ASI heartbeat\" loop that owns scheduling for the whole agent. Each subsystem declares an interval in one dictionary, last-run timestamps persist to a JSON state file, and capabilities are invoked as direct function calls rather than subprocesses, so cadence survives restarts and can be changed with a one-line edit.", "body_md": "*Why an autonomous desktop agent should own a clock instead of outsourcing it to the operating system's job runner — and what that buys you when things fail.*\n\nIf you run an agent that is supposed to keep working while you sleep, you eventually face a scheduling problem. The obvious answer is the system's own job runner: cron on Linux, Task Scheduler on Windows. Start with five jobs. Then twelve. Then you are at eighty-four, each with its own interval, its own log, its own way of failing silently — and no single place where you can answer the only question that matters at 3am: *what is actually running, and what is stuck?*\n\nOpenAmer is an open-source desktop AI agent (Apache-2.0) that runs locally on Windows. This post is the deep-dive I get asked for most: the **ASI heartbeat** — a single in-process loop that replaced our pile of scheduled jobs.\n\nJob-per-concern looks clean in a diagram and rots in practice, for three reasons:\n\nThe deeper issue is architectural: **the schedule lives outside the agent.** The agent's own logic cannot see, reason about, or adjust its own cadence. That is backwards for a system whose whole job is to keep itself running.\n\nThe heartbeat is a single loop that owns cadence for the whole system. Each **subsystem** declares an interval; the loop checks whether it is *due* and, if so, runs it — in the same process, via a direct function call.\n\n```\n# tools/asi/heartbeat.py  (conceptually)\nclass Heartbeat:\n    SUBSYSTEMS = {\n        \"learning\": 300,   # every 5 min\n        \"system\":   300,   # self-heal, resource monitor\n        \"senses\":   1800,  # circadian, trend scout\n        \"swarm\":    1800,\n        \"infra\":    1800,  # env check, browser, plugin, mesh\n        \"meta\":     3600,  # reflection, goal, research\n        \"security\": 14400, # bugbot, CVE scan, code review\n        \"a2a\":      14400, # brain export, peer comms\n        \"darwin\":   900,   # evolution, autopatch, publish, probe\n        \"outreach\": 10800,\n    }\n\n    def tick(self, system=None, force=False):\n        for name, interval in self.SUBSYSTEMS.items():\n            if system and name != system:\n                continue\n            if force or self.is_due(name, interval):\n                self.run(name)          # direct call, not a subprocess\n                self.mark_ran(name)     # persisted timestamp\n```\n\nLast-run timestamps live in a small JSON state file (`memory/asi_heartbeat.json`), so the cadence survives across process restarts. The loop is driven by **one** scheduler entry — `asi-heartbeat-tick`, every five minutes — and every subsystem rides on top of it.\n\nThe subsystems are not arbitrary; they are the organs the agent needs to stay alive and improve:\n\n| Subsystem | Cadence | What it covers | \n|---|---|---|\n| `learning` | 5m | internet learner, active learn, knowledge transfer | \n| `system` | 5m | self-healer, resource monitor, traffic cop | \n| `darwin` | 15m | evolution, autopatch, publish, probe | \n| `senses` | 30m | circadian rhythm, watchtower, trend scout | \n| `swarm` | 30m | swarm intelligence, autonomous loop | \n| `infra` | 30m | env check, browser, plugin, mesh, cache | \n| `meta` | 60m | reflection, self-rewriter, goal, research | \n| `security` | 240m | bugbot, CVE scan, pen test, code review | \n| `a2a` | 240m | brain export, peer communication | \n| `outreach` | 180m | social, GitHub, growth report, funding | \n\nCadence lives in one dict. Changing \"how often does the agent learn from the internet\" is a one-line edit, not a hunt through a job runner's UI.\n\nThe heartbeat is only half the story. The other half is *what a job runs*.\n\nPreviously each capability was an external script invoked as a process:\n\n``` python\n# BEFORE: a process per capability\nimport subprocess\nsubprocess.run([sys.executable, \"scripts/training/self_model.py\"])\n```\n\nNow each subsystem is imported and called directly:\n\n``` python\n# AFTER: a function call\nfrom tools.asi import self_model\nstate = self_model.gather_state()\n```\n\nThree things fall out of this:\n\nThe same five capabilities are also exposed as native agent tools — `asi_status`, `asi_think`, `asi_learn`, `asi_remember`, `asi_trigger` — and as a CLI:\n\n```\nopenamer asi status      # full system + heartbeat health\nopenamer asi heartbeat   # tick the loop (optionally one subsystem)\nopenamer asi trigger <capability>\n```\n\nThis is not free lunch, and the trade is deliberate:\n\nThe point is not that a heartbeat is clever. It is that **an autonomous system should own its own clock, its own state and its own health — in one place it can measure.** Once scheduling is a function call inside the agent, \"is the agent healthy?\" stops being an archaeology exercise across a job runner and becomes a single status query.\n\nCode and the heartbeat module live here: **[https://github.com/openamer/openamer](https://github.com/openamer/openamer)** — the scheduler is under `tools/asi/heartbeat.py`.\n\nIf you've run a long-lived agent in production: what finally made you move scheduling *into* the system, or what made you keep it outside? I'm especially curious about the failure-isolation patterns people land on.", "url": "https://wpnews.pro/news/one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent", "canonical_source": "https://dev.to/openamer/one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent-2i6l", "published_at": "2026-10-10 22:07:21+00:00", "updated_at": "2026-10-10 22:16:14.959371+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "mlops"], "entities": ["OpenAmer", "Windows", "Linux", "Task Scheduler", "cron"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent", "markdown": "https://wpnews.pro/news/one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent.md", "text": "https://wpnews.pro/news/one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent.txt", "jsonld": "https://wpnews.pro/news/one-heartbeat-not-84-cron-jobs-the-scheduler-at-the-core-of-a-self-hosted-agent.jsonld"}}