[AI Readability Summary] Nanobot’s HeartbeatService replaces hardcoded rules with LLM tool calling to handle recurring wake-ups, task decisions, and proactive notifications. It solves a key limitation of traditional timers: they can schedule execution, but they cannot understand natural-language tasks. Keywords: HeartbeatService, HEARTBEAT.md, Tool Calling.
The technical snapshot captures the system at a glance
| Parameter | Description |
|---|---|
| Core language | Python |
| Concurrency model | asyncio |
| Decision protocol | LLM Tool Calling / Function Call |
| Task carrier | HEARTBEAT.md |
| Scheduling interval | 30 minutes by default |
| Code size | HeartbeatService is under 200 lines |
| Comparison targets | OpenClaw / Claw0 / ZeroClaw |
| Core dependencies | LLM Provider, MessageBus, AgentLoop |
| Star count | Not provided in the source article |
HeartbeatService turns an Agent from reactive response into proactive execution
Nanobot designs recurring tasks as an extremely lightweight background capability: it periodically reads HEARTBEAT.md, then lets the LLM decide whether any active task currently exists. This design avoids hardcoded rules and does not depend on fragile keyword conventions.
Compared with a traditional Cron job that only answers “run at this time,” Heartbeat behaves more like “think at this time.” It makes a decision first, then determines whether to invoke the main Agent to execute the task. That makes it a better fit for monitoring, inspections, state synchronization, and anomaly alerts.
It solves task understanding, not just scheduling
Many systems can run on a schedule, but they cannot understand a natural-language instruction such as “check the logs every so often, and notify me if anything looks abnormal.” The key breakthrough in HeartbeatService is that it turns the task entry point into a Markdown file and delegates rule interpretation to the LLM.
async def _tick(self) -> None:
content = self._read_heartbeat_file() # Read HEARTBEAT.md
if not content:
return # Skip directly if the file does not exist or is empty
action, tasks = await self._decide(content) # Let the LLM make the decision
if action != "run":
return # Do not execute if there is no active task
if self.on_execute:
response = await self.on_execute(tasks) # Execute the task
if response and self.on_notify:
await self.on_notify(response) # Send a notification
This snippet shows the smallest complete Heartbeat loop: read, decide, execute, notify.
The two-phase execution model significantly reduces Heartbeat service coupling
This design is split into Phase 1 and Phase 2. Phase 1 only answers “should anything happen?” Phase 2 answers “what should happen in practice?” This separation prevents the Heartbeat module from binding directly to business logic.
Phase 1 takes the contents of HEARTBEAT.md as input and returns a structured decision: skip or run. Phase 2 uses the on_execute and on_notify callbacks to delegate task execution and message delivery to external modules.
Phase 1 uses a virtual tool to constrain LLM output
To prevent the model from returning free-form text that is hard to parse, Nanobot defines _HEARTBEAT_TOOL for Heartbeat. The LLM must return action and tasks through a tool call, so the code only needs to read parameters instead of performing fragile text matching.
_HEARTBEAT_TOOL = [{
"type": "function",
"function": {
"name": "heartbeat",
"description": "Report heartbeat decision after reviewing tasks.",
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["skip", "run"] # Constrain the decision result
},
"tasks": {
"type": "string" # Return the task summary when action is run
}
},
"required": ["action"]
}
}
}]
At its core, this is not really about “tool capability.” It is about an “output contract.”
HEARTBEAT.md serves as the human-Agent collaboration interface for tasks
HEARTBEAT.md is not an ordinary documentation file. It is the task source for Heartbeat. Users, Agents, or skill modules can all modify it and turn long-running, recurring intent into text-based tasks.
Compared with storing tasks in a database or a complex DSL, Markdown offers readability, editability, and version control. It is naturally well suited to personal Agent scenarios and lowers the maintenance barrier for automation.
# Heartbeat Tasks
This file is checked every 30 minutes by your nanobot agent.
## Active Tasks
- Check the service logs every 30 minutes. If errors appear, prepare a summary and notify me.
- Inspect API availability every day. If failures continue, provide diagnostic suggestions.
## Completed
- Completed tasks can be moved here
This file acts as both a task list and the Agent’s recurring memory entry point.
Claw0 and ZeroClaw reveal two design paths for Heartbeat
Claw0 represents the classic background Heartbeat pattern built on threads, locks, and precondition chains. It emphasizes user priority and uses lane_lock to ensure foreground user input always takes precedence over background tasks, preventing Heartbeat from interrupting the main conversation flow.
ZeroClaw is closer to a daemon-style architecture: Gateway, Channels, Heartbeat, and Scheduler run in parallel, while a Supervisor handles fault recovery. It shows where Heartbeat sits from an engineering perspective inside a more complete Agent runtime.
The user-first principle is a hard constraint in Heartbeat design
In Claw0, the main interaction flow and the Heartbeat flow share the same lock: the user channel acquires it in blocking mode, while the Heartbeat channel acquires it in non-blocking mode. When the user is active, Heartbeat yields automatically. This better matches the experience requirements of an interactive Agent than a simple timer trigger.
lane_lock = threading.Lock()
# Main user channel: blocking acquire, highest priority
lane_lock.acquire()
try:
pass # Process the user request
finally:
lane_lock.release()
# Heartbeat channel: non-blocking acquire, skip on failure
acquired = lane_lock.acquire(blocking=False)
if not acquired:
return # User is active, abandon this Heartbeat cycle
This logic defines the runtime rule that background work must not preempt the foreground.
The boundary between HeartbeatService and CronService must remain explicit
CronService handles scheduling based on fixed time expressions such as cron, every, and at. It focuses on “when to run.” HeartbeatService reads task context and decides whether execution is worthwhile. It focuses on “whether action should happen now.”
As a result, they are not substitutes. They are complementary: Cron is better for precise scheduling, while Heartbeat is better for natural-language-driven inspections and Agent tasks.
The image only shows promotional book cover information for this article series
AI Visual Insight: This image is a promotional graphic for a technical book cover. It mainly communicates the author’s publication information and brand exposure. It does not contain data structures, flowcharts, or module relationships that help explain HeartbeatService, the scheduling flow, or the Agent architecture, so it adds no direct technical value for understanding the source code.
Heartbeat’s engineering lesson is to achieve powerful semantic scheduling with minimal code
Nanobot’s implementation proves a key point: in Agent systems, the expensive part is not the timer itself, but semantic task parsing. If you delegate the “should this run” decision to the LLM and constrain the return format with tool calling, you can achieve strong generality with very little code.
This design is especially suitable for personal assistants, autonomous inspections, alert analysis, and lightweight operations Agents. If future iterations add memory, permissions, and multi-channel delivery, Heartbeat can evolve into the continuous perception layer of an Agent.
FAQ: The 3 questions developers care about most
Q1: Why not use regex or keywords to evaluate HEARTBEAT.md?
A: Because recurring tasks are usually described in natural language, rule matching becomes fragile and hard to maintain. LLM + Tool Calling can reliably produce structured decisions while preserving semantic understanding.
Q2: How should HeartbeatService and CronService divide responsibilities?
A: CronService handles precise time-based triggers, while HeartbeatService handles semantic evaluation of task existence and execution necessity. The former is scheduling-oriented; the latter is intelligence-oriented.
Q3: What is the biggest benefit of the two-phase execution model?
A: It completely decouples task judgment from task execution. The Heartbeat service only handles wake-up and decision-making, while the main Agent, message bus, and channel system take over actual execution, which improves extensibility.
Core Summary: This article reconstructs the OpenClaw Heartbeat design from the Nanobot source code. It explains HEARTBEAT.md, virtual tool calling, and the two-phase execution model, showing how fewer than 200 lines of code can implement recurring checks, task decisions, and proactive notifications for an AI Agent.