2.2 KiB
2.2 KiB
Heartbeat Scheduling
Combines OpenClaw's HEARTBEAT.md task format with Paperclip's interval-based heartbeat model. Designed for Claude Code agents running on a schedule.
How it works
- A scheduler (cron/launchd/systemd) runs
heartbeat-runner.shat a fixed interval (e.g., every 30 minutes) - The runner reads
HEARTBEAT.mdfor task definitions - Emptiness detection: if the file has no real tasks, skip the API call entirely (saves cost — from OpenClaw)
- For each task: check if it's due based on its interval and last-run time
- Run due tasks via
claude -pwith the task's prompt - Suppress short acknowledgment responses (<300 chars containing HEARTBEAT_OK)
- Update
.heartbeat-state.jsonwith last-run timestamps
Two execution types (from OpenClaw)
systemEvent
Injects a text event into an existing session. Lightweight, no new session.
Use for: notifications, status checks, simple updates.
Template: scripts/templates/cron/system-event.sh
agentTurn
Fires a full agent turn with its own session. Full context, full tool access.
Use for: background autonomous work, complex tasks, multi-step operations.
Template: scripts/templates/cron/agent-turn.sh
Startup catchup (OpenClaw pattern)
When the runner starts after downtime (e.g., machine was off):
- Run
heartbeat-runner.sh --catchup - Processes up to 5 missed tasks
- 5-second stagger between tasks (prevents thundering herd)
Cost optimization
- Emptiness detection: No API call if HEARTBEAT.md has no real content
- ackMaxChars suppression: Responses under 300 chars with HEARTBEAT_OK are logged but not displayed (saves downstream processing)
- Interval-based: Only run tasks when actually due, not every heartbeat
Example cron entries
# Run heartbeat every 30 minutes
*/30 * * * * /path/to/heartbeat-runner.sh >> /tmp/heartbeat.log 2>&1
# Run heartbeat every hour with catchup on restart
@reboot /path/to/heartbeat-runner.sh --catchup >> /tmp/heartbeat.log 2>&1
0 * * * * /path/to/heartbeat-runner.sh >> /tmp/heartbeat.log 2>&1
State file format
.heartbeat-state.json:
{
"email-check": { "last_run": 1712847600 },
"report-generation": { "last_run": 1712844000 }
}