Keep Your Agents Proactive with Heartbeat
Overview
Section titled “Overview”Heartbeat vs Cron? See Cron vs Heartbeat for guidance on when to use each.
Heartbeat runs periodic agent turns in your main session. This allows the model to surface anything that needs your attention without sending too many messages.
A heartbeat is a scheduled turn in the main session. It does not create background task records. You should use task records for detached work like ACP runs, subagents, or isolated cron jobs.
If you need help, check out the troubleshooting guide: /automation/troubleshooting
Quick start (beginner)
Section titled “Quick start (beginner)”- Leave heartbeats enabled. The default is
30m, or1hif you use Anthropic OAuth/setup-token. You can also set your own cadence. - You should create a small
HEARTBEAT.mdchecklist in your agent workspace. This is optional but recommended. - Pick where heartbeat messages go. The default is
target: "none", but you can settarget: "last"to route them to the last contact. - You can enable heartbeat reasoning delivery if you want more transparency.
- You can use lightweight bootstrap context if your heartbeat runs only need
HEARTBEAT.md. - You can enable isolated sessions. This avoids sending the full conversation history with each heartbeat.
- You can restrict heartbeats to your active hours in local time.
Example config:
{ agents: { defaults: { heartbeat: { every: "30m", target: "last", // explicit delivery to last contact (default is "none") directPolicy: "allow", // default: allow direct/DM targets; set "block" to suppress lightContext: true, // optional: only inject HEARTBEAT.md from bootstrap files isolatedSession: true, // optional: fresh session each run (no conversation history) // activeHours: { start: "08:00", end: "24:00" }, // includeReasoning: true, // optional: send separate `Reasoning:` message too }, }, },}Defaults
Section titled “Defaults”- Interval:
30m. It changes to1hwhen the system detects Anthropic OAuth/setup-token auth. You can setagents.defaults.heartbeat.everyor useagents.list[].heartbeat.everyfor specific agents. Set it to0mto turn it off. - Prompt body: You can configure this via
agents.defaults.heartbeat.prompt. The default is:Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK. - The system sends the heartbeat prompt verbatim as a user message. The system prompt includes a “Heartbeat” section and the run is flagged internally.
- The system checks your active hours (
heartbeat.activeHours) using your configured timezone. If you are outside the window, it skips heartbeats until the next scheduled time inside the window.
What the heartbeat prompt is for
Section titled “What the heartbeat prompt is for”The default prompt is broad to cover two main areas:
- Background tasks: It uses “Consider outstanding tasks” to nudge the agent. This helps it review follow-ups like your inbox, calendar, or queued work and bring up urgent items.
- Human check-in: It uses “Checkup sometimes on your human during day time” for light check-ins. It avoids sending messages at night by using your local timezone (see /concepts/timezone).
Heartbeat can react to background tasks that are finished, but the heartbeat run itself does not create a task record.
If you need a heartbeat to do a specific job, like checking Gmail stats or verifying gateway health, you can set a custom body in agents.defaults.heartbeat.prompt or agents.list[].heartbeat.prompt. This is sent verbatim.
Response contract
Section titled “Response contract”- If the agent finds nothing that needs attention, it should reply with
HEARTBEAT_OK. - During these runs, OpenClaw looks for
HEARTBEAT_OKat the start or end of the reply. It strips the token and drops the reply if the remaining text is ≤ackMaxChars(the default is 300). - If
HEARTBEAT_OKappears in the middle of a reply, the system does not treat it as a special signal. - When sending alerts, the agent should not include
HEARTBEAT_OK. It should only return the alert text.
If a stray HEARTBEAT_OK appears at the start or end of a message outside of a heartbeat run, the system strips and logs it. If the message is only HEARTBEAT_OK, it is dropped.
Config
Section titled “Config”You can set up heartbeats in your configuration to keep your agents active and responsive. Here is how the basic structure looks in your config.json5:
{ agents: { defaults: { heartbeat: { every: "30m", // default: 30m (0m disables) model: "anthropic/claude-opus-4-6", includeReasoning: false, // default: false (deliver separate Reasoning: message when available) lightContext: false, // default: false; true keeps only HEARTBEAT.md from workspace bootstrap files isolatedSession: false, // default: false; true runs each heartbeat in a fresh session (no conversation history) target: "last", // default: none | options: last | none | <channel id> (core or plugin, e.g. "bluebubbles") to: "+15551234567", // optional channel-specific override accountId: "ops-bot", // optional multi-account channel id prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.", ackMaxChars: 300, // max chars allowed after HEARTBEAT_OK }, }, },}Scope and precedence
Section titled “Scope and precedence”You should keep in mind how these settings layer over each other:
agents.defaults.heartbeathandles your global heartbeat behavior.agents.list[].heartbeatmerges on top. If you give an agent aheartbeatblock, only those agents will run heartbeats.channels.defaults.heartbeatsets the visibility defaults for all your channels.channels.<channel>.heartbeatlets you override those channel defaults.channels.<channel>.accounts.<id>.heartbeatis for multi-account channels and overrides per-channel settings.
Per-agent heartbeats
Section titled “Per-agent heartbeats”If you include a heartbeat block in any agents.list[] entry, the system assumes only those specific agents need to run heartbeats. This block merges with your agents.defaults.heartbeat, so you can define shared settings once and just tweak what you need for each agent.
In this example, only the second agent runs heartbeats:
{ agents: { defaults: { heartbeat: { every: "30m", target: "last", // explicit delivery to last contact (default is "none") }, }, list: [ { id: "main", default: true }, { id: "ops", heartbeat: { every: "1h", target: "whatsapp", to: "+15551234567", prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.", }, }, ], },}Active hours example
Section titled “Active hours example”You might want to limit heartbeats to business hours in a specific timezone. You can do that like this:
{ agents: { defaults: { heartbeat: { every: "30m", target: "last", // explicit delivery to last contact (default is "none") activeHours: { start: "09:00", end: "22:00", timezone: "America/New_York", // optional; uses your userTimezone if set, otherwise host tz }, }, }, },}If it is before 9am or after 10pm Eastern, the system skips the heartbeat. The next scheduled tick inside that window will run normally.
24/7 setup
Section titled “24/7 setup”If you need heartbeats running around the clock, you have two options:
- Just leave out
activeHoursentirely. This is the default behavior. - Set a full-day window:
activeHours: { start: "00:00", end: "24:00" }.
Avoid setting the start and end to the exact same time (like 08:00 to 08:00). The system sees that as a zero-width window, so heartbeats are always skipped.
Multi account example
Section titled “Multi account example”When you use multi-account channels like Telegram, use accountId to point to a specific account:
{ agents: { list: [ { id: "ops", heartbeat: { every: "1h", target: "telegram", to: "12345678:topic:42", // optional: route to a specific topic/thread accountId: "ops-bot", }, }, ], }, channels: { telegram: { accounts: { "ops-bot": { botToken: "YOUR_TELEGRAM_BOT_TOKEN" }, }, }, },}Field notes
Section titled “Field notes”every: This is your heartbeat interval. It is a duration string, and the default unit is minutes.model: Use this if you want to override the model for heartbeat runs using theprovider/modelformat.includeReasoning: When you turn this on, you get a separateReasoning:message when it is available (same as/reasoning on).lightContext: When this is true, the heartbeat uses a lightweight bootstrap context and only keepsHEARTBEAT.mdfrom your workspace files.isolatedSession: This runs each heartbeat in a fresh session with no prior history. It uses the same isolation pattern as cronsessionTarget: "isolated". This is great for saving tokens. You can pair it withlightContext: truefor maximum savings. Note that delivery routing still uses the main session context.session: This is an optional session key for your heartbeat runs.main(default): The agent’s main session.- You can also use an explicit session key from
openclaw sessions --jsonor the sessions CLI. - Check Sessions and Groups for key formats.
target:last: Sends to the last external channel you used.- Explicit channel: Any configured channel or plugin ID, like
discord,matrix,telegram, orwhatsapp. none(default): The heartbeat runs, but do not deliver externally.
directPolicy: This manages DM delivery behavior:allow(default): DMs are allowed.block: Stops DM delivery with areason=dm-blockedstatus.
to: An optional recipient override. This could be a channel-specific ID, like E.164 for WhatsApp or a Telegram chat ID. For Telegram topics, use<chatId>:topic:<messageThreadId>.accountId: Use this for multi-account channels. If you usetarget: "last", it applies to the resolved channel if that channel supports accounts. If the ID doesn’t match a configured account, the delivery is skipped.prompt: This replaces the default prompt body and is not merged.ackMaxChars: This sets the max characters allowed afterHEARTBEAT_OKbefore delivery happens.suppressToolErrorWarnings: Set this to true to hide tool error warning payloads during heartbeat runs.activeHours: This limits heartbeats to a specific window. It is an object withstart(HH:MM, inclusive),end(HH:MM, exclusive), and an optionaltimezone.- If you omit this or use
"user", it uses youragents.defaults.userTimezoneor the host system time. "local"always uses the host system timezone.- You can use any IANA identifier like
America/New_York. - Remember that
startandendcannot be equal. - Heartbeats are skipped outside this window until the next tick inside the window.
- If you omit this or use
Delivery behavior
Section titled “Delivery behavior”- By default, heartbeats run in the agent’s main session (
agent:<id>:<mainKey>). Ifsession.scopeis set to"global", they run there instead. You can usesessionto point to a specific channel session like Discord or WhatsApp. - The
sessionsetting only changes the context of the run. Where the message actually goes is controlled bytargetandto. - If you want to send to a specific place, set both
targetandto. Usingtarget: "last"will send the message to the last external channel used in that session. - Heartbeats allow DM targets by default. You can use
directPolicy: "block"to stop these sends while still letting the heartbeat run. - If the main queue is busy, the system skips the heartbeat and tries again later.
- If your
targetdoesn’t resolve to a real destination, the heartbeat still runs, but no message is sent out. - Heartbeat-only replies do not keep a session alive. The system restores the last
updatedAttimestamp so your idle expiry settings still work correctly. - If you have detached background tasks, they can trigger a system event to wake the heartbeat if the main session needs to notice something fast. This wake doesn’t force the heartbeat to run a background task itself.
Visibility controls
Section titled “Visibility controls”Managing how often your agent pings you is key to avoiding notification fatigue. By default, OpenClaw keeps things quiet by suppressing HEARTBEAT_OK acknowledgments while still delivering actual alert content. You can customize this behavior for specific channels or even individual accounts:
channels: defaults: heartbeat: showOk: false # Hide HEARTBEAT_OK (default) showAlerts: true # Show alert messages (default) useIndicator: true # Emit indicator events (default) telegram: heartbeat: showOk: true # Show OK acknowledgments on Telegram whatsapp: accounts: work: heartbeat: showAlerts: false # Suppress alert delivery for this accountThe settings follow a clear hierarchy: per-account overrides per-channel, which overrides channel defaults, which finally overrides the built-in defaults.
What each flag does
Section titled “What each flag does”showOk: sends aHEARTBEAT_OKacknowledgment when the model returns an OK-only reply.showAlerts: sends the alert content when the model returns a non-OK reply.useIndicator: emits indicator events for UI status surfaces.
If you set all three to false, OpenClaw skips the heartbeat run entirely. This means no model call happens, saving you API costs.
Per-channel vs per-account examples
Section titled “Per-channel vs per-account examples”You can mix and match these settings to ensure you only get notified where it matters. For instance, you might want OK confirmations on Slack but not on other platforms.
channels: defaults: heartbeat: showOk: false showAlerts: true useIndicator: true slack: heartbeat: showOk: true # all Slack accounts accounts: ops: heartbeat: showAlerts: false # suppress alerts for the ops account only telegram: heartbeat: showOk: trueCommon patterns
Section titled “Common patterns”| Goal | Config |
|---|---|
| Default behavior (silent OKs, alerts on) | (no config needed) |
| Fully silent (no messages, no indicator) | channels.defaults.heartbeat: { showOk: false, showAlerts: false, useIndicator: false } |
| Indicator-only (no messages) | channels.defaults.heartbeat: { showOk: false, showAlerts: false, useIndicator: true } |
| OKs in one channel only | channels.telegram.heartbeat: { showOk: true } |
HEARTBEAT.md (optional)
Section titled “HEARTBEAT.md (optional)”If you place a HEARTBEAT.md file in your workspace, the default prompt tells the agent to read it. You can think of this as your “heartbeat checklist”—a small, stable set of reminders that are safe to include every 30 minutes.
If HEARTBEAT.md exists but is effectively empty (containing only blank lines or markdown headers like # Heading), OpenClaw skips the heartbeat run to save API calls. If the file is missing entirely, the heartbeat still runs and the model decides what to do based on its general instructions.
You should keep this file tiny. A short checklist or a few reminders work best to avoid prompt bloat.
Example HEARTBEAT.md:
# Heartbeat checklist
- Quick scan: anything urgent in inboxes?- If it’s daytime, do a lightweight check-in if nothing else is pending.- If a task is blocked, write down _what is missing_ and ask Peter next time.Can the agent update HEARTBEAT.md?
Section titled “Can the agent update HEARTBEAT.md?”Yes, you can ask it to do this.
Since HEARTBEAT.md is just a normal file in the agent workspace, you can tell the agent in a standard chat to modify it. You might say:
- “Update
HEARTBEAT.mdto add a daily calendar check.” - “Rewrite
HEARTBEAT.mdso it’s shorter and focused on inbox follow-ups.”
If you want this to happen without your intervention, you can include an explicit instruction in your heartbeat prompt like: “If the checklist becomes stale, update HEARTBEAT.md with a better one.”
One safety note: do not put secrets like API keys, phone numbers, or private tokens into HEARTBEAT.md. It becomes part of the prompt context and could be exposed.
Manual wake (on-demand)
Section titled “Manual wake (on-demand)”Sometimes you need to trigger a heartbeat right away instead of waiting for the schedule. You can enqueue a system event and force an immediate response using this command:
openclaw system event --text "Check for urgent follow-ups" --mode nowIf you have multiple agents with heartbeat configured, running a manual wake triggers each of those agent heartbeats at the same time.
If you aren’t in a rush, you can use --mode next-heartbeat to wait for the next scheduled tick.
Reasoning delivery (optional)
Section titled “Reasoning delivery (optional)”By default, heartbeats only send you the final “answer” payload.
If you want more transparency into how the agent is thinking, you can enable this option:
agents.defaults.heartbeat.includeReasoning: true
When you turn this on, heartbeats deliver a separate message prefixed with Reasoning: (it follows the same format as /reasoning on). This is helpful if the agent is managing several sessions or codexes and you want to see exactly why it decided to ping you. Keep in mind that this can reveal more internal details than you might want, so you should probably keep it turned off in group chats.
Cost awareness
Section titled “Cost awareness”Heartbeats run full agent turns. This means shorter intervals will burn through your tokens faster. To keep your costs under control, you should try these settings:
- Use
isolatedSession: true. This avoids sending the full conversation history. You can see usage drop from roughly 100K tokens to about 2-5K per run. - Use
lightContext: true. This limits bootstrap files to justHEARTBEAT.md. - Set a cheaper
model. For example,ollama/llama3.2:1bis a good choice. - Keep your
HEARTBEAT.mdfile small. - Use
target: "none"if you only want internal state updates.
Related
Section titled “Related”- Automation Overview — all automation mechanisms at a glance
- Cron vs Heartbeat — when to use each
- Background Tasks — how detached work is tracked
- Timezone — how timezone affects heartbeat scheduling
- Troubleshooting — debugging automation issues
OpenClaw Expert
Still stuck?
If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.