2025-12-09 17:51:05 +00:00
|
|
|
|
---
|
2026-01-08 23:06:56 +01:00
|
|
|
|
summary: "Heartbeat polling messages and notification rules"
|
2025-12-09 17:51:05 +00:00
|
|
|
|
read_when:
|
|
|
|
|
|
- Adjusting heartbeat cadence or messaging
|
|
|
|
|
|
---
|
2025-12-26 02:35:21 +01:00
|
|
|
|
# Heartbeat (Gateway)
|
2025-11-26 17:05:09 +01:00
|
|
|
|
|
2026-01-08 23:06:56 +01:00
|
|
|
|
Heartbeat runs **periodic agent turns** in the main session so the model can
|
|
|
|
|
|
surface anything that needs attention without spamming you.
|
2025-11-26 17:05:09 +01:00
|
|
|
|
|
2026-01-10 22:26:20 +00:00
|
|
|
|
## Quick start (beginner)
|
|
|
|
|
|
|
|
|
|
|
|
1. Leave heartbeats enabled (default is `30m`) or set your own cadence.
|
|
|
|
|
|
2. Create a tiny `HEARTBEAT.md` checklist in the agent workspace (optional but recommended).
|
|
|
|
|
|
3. Decide where heartbeat messages should go (`target: "last"` is the default).
|
|
|
|
|
|
4. Optional: enable heartbeat reasoning delivery for transparency.
|
|
|
|
|
|
|
|
|
|
|
|
Example config:
|
|
|
|
|
|
|
|
|
|
|
|
```json5
|
|
|
|
|
|
{
|
|
|
|
|
|
agents: {
|
|
|
|
|
|
defaults: {
|
|
|
|
|
|
heartbeat: {
|
|
|
|
|
|
every: "30m",
|
|
|
|
|
|
target: "last",
|
|
|
|
|
|
// includeReasoning: true, // optional: send separate `Reasoning:` message too
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-06 22:28:32 +00:00
|
|
|
|
## Defaults
|
2026-01-08 23:06:56 +01:00
|
|
|
|
|
2026-01-09 12:44:23 +00:00
|
|
|
|
- Interval: `30m` (set `agents.defaults.heartbeat.every`; use `0m` to disable).
|
|
|
|
|
|
- Prompt body (configurable via `agents.defaults.heartbeat.prompt`):
|
2026-01-06 22:28:32 +00:00
|
|
|
|
`Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`
|
2026-01-08 23:06:56 +01:00
|
|
|
|
- The heartbeat prompt is sent **verbatim** as the user message. The system
|
|
|
|
|
|
prompt includes a “Heartbeat” section and the run is flagged internally.
|
|
|
|
|
|
|
2026-01-10 22:26:20 +00:00
|
|
|
|
## What the heartbeat prompt is for
|
|
|
|
|
|
|
|
|
|
|
|
The default prompt is intentionally broad:
|
|
|
|
|
|
- **Background tasks**: “Consider outstanding tasks” nudges the agent to review
|
|
|
|
|
|
follow-ups (inbox, calendar, reminders, queued work) and surface anything urgent.
|
|
|
|
|
|
- **Human check-in**: “Checkup sometimes on your human during day time” nudges an
|
|
|
|
|
|
occasional lightweight “anything you need?” message, but avoids night-time spam
|
|
|
|
|
|
by using your configured local timezone (see [/concepts/timezone](/concepts/timezone)).
|
|
|
|
|
|
|
|
|
|
|
|
If you want a heartbeat to do something very specific (e.g. “check Gmail PubSub
|
|
|
|
|
|
stats” or “verify gateway health”), set `agents.defaults.heartbeat.prompt` to a
|
|
|
|
|
|
custom body (sent verbatim).
|
|
|
|
|
|
|
2026-01-08 23:06:56 +01:00
|
|
|
|
## Response contract
|
|
|
|
|
|
|
|
|
|
|
|
- If nothing needs attention, reply with **`HEARTBEAT_OK`**.
|
|
|
|
|
|
- During heartbeat runs, Clawdbot treats `HEARTBEAT_OK` as an ack when it appears
|
|
|
|
|
|
at the **start or end** of the reply. The token is stripped and the reply is
|
|
|
|
|
|
dropped if the remaining content is **≤ `ackMaxChars`** (default: 30).
|
|
|
|
|
|
- If `HEARTBEAT_OK` appears in the **middle** of a reply, it is not treated
|
|
|
|
|
|
specially.
|
|
|
|
|
|
- For alerts, **do not** include `HEARTBEAT_OK`; return only the alert text.
|
|
|
|
|
|
|
|
|
|
|
|
Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped
|
|
|
|
|
|
and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
2026-01-05 19:43:54 +01:00
|
|
|
|
|
2025-12-26 02:35:21 +01:00
|
|
|
|
## Config
|
|
|
|
|
|
|
|
|
|
|
|
```json5
|
|
|
|
|
|
{
|
2026-01-09 12:44:23 +00:00
|
|
|
|
agents: {
|
|
|
|
|
|
defaults: {
|
|
|
|
|
|
heartbeat: {
|
|
|
|
|
|
every: "30m", // default: 30m (0m disables)
|
|
|
|
|
|
model: "anthropic/claude-opus-4-5",
|
2026-01-10 22:26:20 +00:00
|
|
|
|
includeReasoning: false, // default: false (deliver separate Reasoning: message when available)
|
2026-01-09 12:44:23 +00:00
|
|
|
|
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
|
|
|
|
|
|
to: "+15551234567", // optional provider-specific override
|
|
|
|
|
|
prompt: "Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.",
|
|
|
|
|
|
ackMaxChars: 30 // max chars allowed after HEARTBEAT_OK
|
|
|
|
|
|
}
|
2025-12-26 02:35:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-08 23:06:56 +01:00
|
|
|
|
### Field notes
|
|
|
|
|
|
|
|
|
|
|
|
- `every`: heartbeat interval (duration string; default unit = minutes).
|
2025-12-26 02:35:21 +01:00
|
|
|
|
- `model`: optional model override for heartbeat runs (`provider/model`).
|
2026-01-10 22:26:20 +00:00
|
|
|
|
- `includeReasoning`: when enabled, also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`).
|
2026-01-08 23:06:56 +01:00
|
|
|
|
- `target`:
|
|
|
|
|
|
- `last` (default): deliver to the last used external provider.
|
|
|
|
|
|
- explicit provider: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage`.
|
|
|
|
|
|
- `none`: run the heartbeat but **do not deliver** externally.
|
|
|
|
|
|
- `to`: optional recipient override (E.164 for WhatsApp, chat id for Telegram, etc.).
|
|
|
|
|
|
- `prompt`: overrides the default prompt body (not merged).
|
|
|
|
|
|
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
|
2025-12-26 02:35:21 +01:00
|
|
|
|
|
2026-01-08 23:06:56 +01:00
|
|
|
|
## Delivery behavior
|
|
|
|
|
|
|
|
|
|
|
|
- Heartbeats run in the **main session** (`main`, or `global` when scope is global).
|
|
|
|
|
|
- If the main queue is busy, the heartbeat is skipped and retried later.
|
|
|
|
|
|
- If `target` resolves to no external destination, the run still happens but no
|
|
|
|
|
|
outbound message is sent.
|
|
|
|
|
|
- Heartbeat-only replies do **not** keep the session alive; the last `updatedAt`
|
|
|
|
|
|
is restored so idle expiry behaves normally.
|
2026-01-06 21:54:19 +00:00
|
|
|
|
|
|
|
|
|
|
## HEARTBEAT.md (optional)
|
2026-01-08 23:06:56 +01:00
|
|
|
|
|
2026-01-06 21:54:19 +00:00
|
|
|
|
If a `HEARTBEAT.md` file exists in the workspace, the default prompt tells the
|
2026-01-10 22:26:20 +00:00
|
|
|
|
agent to read it. Think of it as your “heartbeat checklist”: small, stable, and
|
|
|
|
|
|
safe to include every 30 minutes.
|
|
|
|
|
|
|
|
|
|
|
|
Keep it tiny (short checklist or reminders) to avoid prompt bloat.
|
|
|
|
|
|
|
|
|
|
|
|
Example `HEARTBEAT.md`:
|
|
|
|
|
|
|
|
|
|
|
|
```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?
|
|
|
|
|
|
|
|
|
|
|
|
Yes — if you ask it to.
|
|
|
|
|
|
|
|
|
|
|
|
`HEARTBEAT.md` is just a normal file in the agent workspace, so you can tell the
|
|
|
|
|
|
agent (in a normal chat) something like:
|
|
|
|
|
|
- “Update `HEARTBEAT.md` to add a daily calendar check.”
|
|
|
|
|
|
- “Rewrite `HEARTBEAT.md` so it’s shorter and focused on inbox follow-ups.”
|
|
|
|
|
|
|
|
|
|
|
|
If you want this to happen proactively, you can also include an explicit line in
|
|
|
|
|
|
your heartbeat prompt like: “If the checklist becomes stale, update HEARTBEAT.md
|
|
|
|
|
|
with a better one.”
|
|
|
|
|
|
|
|
|
|
|
|
Safety note: don’t put secrets (API keys, phone numbers, private tokens) into
|
|
|
|
|
|
`HEARTBEAT.md` — it becomes part of the prompt context.
|
2026-01-06 21:54:19 +00:00
|
|
|
|
|
2026-01-08 23:06:56 +01:00
|
|
|
|
## Manual wake (on-demand)
|
|
|
|
|
|
|
|
|
|
|
|
You can enqueue a system event and trigger an immediate heartbeat with:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
clawdbot wake --text "Check for urgent follow-ups" --mode now
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Use `--mode next-heartbeat` to wait for the next scheduled tick.
|
|
|
|
|
|
|
2026-01-10 22:26:20 +00:00
|
|
|
|
## Reasoning delivery (optional)
|
|
|
|
|
|
|
|
|
|
|
|
By default, heartbeats deliver only the final “answer” payload.
|
|
|
|
|
|
|
|
|
|
|
|
If you want transparency, enable:
|
|
|
|
|
|
- `agents.defaults.heartbeat.includeReasoning: true`
|
|
|
|
|
|
|
|
|
|
|
|
When enabled, heartbeats will also deliver a separate message prefixed
|
|
|
|
|
|
`Reasoning:` (same shape as `/reasoning on`). This can be useful when the agent
|
|
|
|
|
|
is managing multiple sessions/codexes and you want to see why it decided to ping
|
|
|
|
|
|
you — but it can also leak more internal detail than you want. Prefer keeping it
|
|
|
|
|
|
off in group chats.
|
|
|
|
|
|
|
2026-01-08 23:06:56 +01:00
|
|
|
|
## Cost awareness
|
|
|
|
|
|
|
|
|
|
|
|
Heartbeats run full agent turns. Shorter intervals burn more tokens. Keep
|
|
|
|
|
|
`HEARTBEAT.md` small and consider a cheaper `model` or `target: "none"` if you
|
|
|
|
|
|
only want internal state updates.
|