Files
clawdbot/docs/heartbeat.md
Peter Steinberger f790f3f3ba fix/heartbeat ok delivery filter (#246)
* cron: skip delivery for HEARTBEAT_OK responses

When an isolated cron job has deliver:true, skip message delivery if the
response is just HEARTBEAT_OK (or contains HEARTBEAT_OK at edges with
short remaining content <= 30 chars). This allows cron jobs to silently
ack when nothing to report but still deliver actual content when there
is something meaningful to say.

Media is still delivered even if text is HEARTBEAT_OK, since the
presence of media indicates there's something to share.

* fix(heartbeat): make ack padding configurable

* chore(deps): update to latest

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-01-05 22:52:13 +00:00

3.3 KiB

summary, read_when
summary read_when
Plan for heartbeat polling messages and notification rules
Adjusting heartbeat cadence or messaging

Heartbeat (Gateway)

Heartbeat runs periodic agent turns in the main session so the model can surface anything that needs attention without spamming the user.

Prompt contract

  • Heartbeat body defaults to HEARTBEAT (configurable via agent.heartbeat.prompt).
  • If nothing needs attention, the model should reply HEARTBEAT_OK.
  • During heartbeat runs, Clawdbot treats HEARTBEAT_OK as an ack when it appears at the start or end of the reply. Clawdbot strips the token and discards the reply if the remaining content is ackMaxChars (default: 30).
  • If HEARTBEAT_OK is in the middle of a reply, it is not treated specially.
  • For alerts, do not include HEARTBEAT_OK; return only the alert text.

Stray HEARTBEAT_OK outside heartbeats

If the model accidentally includes HEARTBEAT_OK at the start or end of a normal (non-heartbeat) reply, Clawdbot strips the token and logs a verbose message. If the reply is only HEARTBEAT_OK, it is dropped.

Outbound normalization (all providers)

For all providers (WhatsApp/Web, Telegram, Slack, Discord, Signal, iMessage), Clawdbot applies the same filtering to tool summaries, streaming block replies, and final replies:

  • drop payloads that are only HEARTBEAT_OK with no media
  • strip HEARTBEAT_OK at the edges when mixed with other text

Config

{
  agent: {
    heartbeat: {
      every: "30m",           // duration string: ms|s|m|h (0m disables)
      model: "anthropic/claude-opus-4-5",
      target: "last",          // last | whatsapp | telegram | none
      to: "+15551234567",      // optional override for whatsapp/telegram
      prompt: "HEARTBEAT",     // optional override
      ackMaxChars: 30          // max chars allowed after HEARTBEAT_OK
    }
  }
}

Fields

  • every: heartbeat interval (duration string; default unit minutes). Omit or set to 0m to disable.
  • model: optional model override for heartbeat runs (provider/model).
  • target: where heartbeat output is delivered.
    • last (default): send to the last used external channel.
    • whatsapp / telegram: force the channel (optionally set to).
    • none: do not deliver externally; output stays in the session (WebChat-visible).
  • to: optional recipient override (E.164 for WhatsApp, chat id for Telegram).
  • prompt: optional override for the heartbeat body (default: HEARTBEAT).
  • ackMaxChars: max chars allowed after HEARTBEAT_OK before delivery (default: 30).

Behavior

  • Runs in the main session (main, or global when scope is global).
  • Uses the main lane queue; if requests are in flight, the wake is retried.
  • Empty output or HEARTBEAT_OK is treated as “ok” and does not keep the session alive (updatedAt is restored).
  • If target resolves to no external destination (no last route or none), the heartbeat still runs but no outbound message is sent.

Wake hook

  • The gateway exposes a heartbeat wake hook so cron/jobs/webhooks can request an immediate run (requestHeartbeatNow).
  • wake endpoints should enqueue system events and optionally trigger a wake; the heartbeat runner picks those up on the next tick or immediately.