A heartbeat is a periodic poll that wakes your OpenClaw agent in its main session and asks: “anything need attention?” If you want an agent that quietly checks your inbox, flags a calendar conflict, or pings you when something interesting shows up without you typing a prompt first, heartbeats are the mechanism. They’re cheap to set up, easy to get wrong, and the difference between a chatbot that waits and an assistant that acts.

This guide covers what heartbeats are, how they differ from cron jobs, how to write a HEARTBEAT.md that stays useful, and the token-cost traps that catch most people.

What a heartbeat actually is

OpenClaw fires a heartbeat message into your agent’s main session on an interval (commonly every 30 minutes, though the timing can drift). The default prompt looks roughly like this:

Read HEARTBEAT.md if it exists. Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.

The agent reads HEARTBEAT.md, runs whatever checks you’ve listed, and either does something useful or replies HEARTBEAT_OK and goes quiet. Because heartbeats run inside the main session, the agent has your recent conversation context, so it can connect “you mentioned a flight Thursday” with “there’s a calendar conflict Thursday morning” without you re-explaining anything.

That shared context is the whole point. It’s also the thing that makes heartbeats expensive if you’re careless, because every heartbeat poll consumes tokens against your main session’s context window.

Heartbeats vs. cron jobs

OpenClaw has two ways to make an agent do things on its own, and people constantly conflate them. They solve different problems.

HeartbeatsCron jobs
Where it runsMain session (has conversation context)Isolated session (fresh context)
TimingApproximate (~every 30 min, can drift)Exact (“9:00 AM Monday”)
Best forBatched checks: inbox + calendar + notifications in one turnOne-off reminders, scheduled reports, nightly deep work
Token costCharged against main session each pollSelf-contained per run
Model/thinking levelSame as main sessionConfigurable per job
OutputReplies in your main chatCan deliver straight to a channel

Rule of thumb: if you need precise timing or want the work isolated from your chat history, use cron jobs. If you want a lightweight “look around and tell me if anything’s up” that benefits from knowing what you’re working on, use a heartbeat. Many setups use both: a heartbeat for ambient awareness, cron for the morning briefing.

Writing a HEARTBEAT.md that works

HEARTBEAT.md lives in your agent’s workspace directory. Keep it short. Every word in it gets read on every poll, and a bloated checklist is a bloated token bill. A workable starting version:

# Heartbeat Checklist

Check these, rotating. Don't do all of them every poll:

- Email: any urgent unread? (urgent = from a known contact, time-sensitive, or flagged)
- Calendar: anything in the next 2 hours I haven't acknowledged?
- Notifications: Twitter/Slack mentions worth surfacing?

When to speak up:
- Important email arrived
- Calendar event <2h away
- It's been >8h since I said anything and there's something worth sharing

When to stay quiet (reply HEARTBEAT_OK):
- Late night (23:00–08:00) unless genuinely urgent
- Nothing new since the last check
- I checked <30 minutes ago

A few things make the difference between a useful heartbeat and an annoying one:

  • Tell it what “urgent” means. Otherwise it surfaces every newsletter. Be specific: which senders matter, what counts as time-sensitive.
  • Tell it when to shut up. Without explicit quiet hours and a “don’t repeat yourself” rule, you get pinged at 3 AM about nothing.
  • Rotate checks instead of running all of them. Email this poll, calendar next poll. Cuts cost roughly in proportion.
  • Track state in a file. Have the agent write memory/heartbeat-state.json with timestamps of the last check per category, so it doesn’t re-do work or re-alert you about the same thing.

A minimal heartbeat-state file

The state file is what stops a heartbeat from being a goldfish. A simple shape:

{
  "lastChecks": {
    "email": 1746000000,
    "calendar": 1745998000,
    "notifications": null
  },
  "lastAlertedEmailId": "abc123",
  "quietSince": 1746010000
}

The agent reads it at the start of a heartbeat, decides what’s due for a check based on the timestamps, does the work, updates the timestamps, and writes it back. Now “I checked email 12 minutes ago” is a fact on disk, not a thing it has to guess from chat history.

Cost: the part people skip

Heartbeats run on a timer whether or not anything happens. At every 30 minutes, that’s 48 polls a day, 1,440 a month, each one reading HEARTBEAT.md, possibly the state file, and carrying your main session’s context. If HEARTBEAT.md is a 2,000-word document and your main session context is large, that adds up fast.

Keep costs sane by:

  1. Keeping HEARTBEAT.md under ~30 lines. It’s a checklist, not a manual.
  2. Widening the interval if 30 minutes is overkill. Hourly is fine for most people.
  3. Rotating checks so each poll does one thing, not five.
  4. Replying HEARTBEAT_OK aggressively. A poll that does nothing should cost almost nothing: the agent reads the checklist, sees nothing’s due, and stops. Don’t let it “think out loud” every time.
  5. Pairing with a cheaper model for the main session if heartbeats are your dominant cost driver. See our API cost reduction tips.

Recent OpenClaw releases tightened heartbeat routing. For example, main-session systemEvent heartbeat wakes now stay on their bound session route instead of leaking to inherited destinations (fixed in the 2026.5.10 betas). The mechanism is stable; the cost discipline is on you.

When heartbeats are the wrong tool

Heartbeats are bad at anything that needs to happen at a specific time. “Send the weekly report at 9 AM Monday” is a cron job; a heartbeat might fire at 8:47 or 9:21, and it’ll carry your whole chat context to do something that needs none of it. Heartbeats are also bad for long-running work; a deep research task that takes 20 minutes shouldn’t run in your main session where it pollutes the context window. Spin that up as an isolated cron job or a background session instead.

Use heartbeats for the ambient stuff: a low-frequency “glance around and tell me if anything matters” that quietly leans on the fact that the agent already knows what you’re up to. For everything that needs precision or isolation, reach for cron.

Getting started

  1. Create HEARTBEAT.md in your agent’s workspace with three or four checks and explicit quiet hours.
  2. Have the agent maintain memory/heartbeat-state.json so it doesn’t repeat itself.
  3. Watch it for a few days. If it’s noisy, tighten the “when to speak up” rules. If it’s expensive, widen the interval or trim the checklist.
  4. Move anything time-sensitive out to a cron job.

A good heartbeat is one you forget exists until the day it catches something — a conflicting meeting, an email you’d have missed — and you realize the agent’s been watching the whole time. For the bigger picture of how OpenClaw agents hold context across sessions, see OpenClaw memory and context, and if you’re new here, what OpenClaw is and how it works cover the fundamentals.

Sources