Heartbeat monitoring¶
User-defined health checks evaluated on a schedule. Configure checks in /Users/{user_id}/{bot_dir}/config/HEARTBEAT.md.
Heartbeat needs the Nextcloud mount — the config file lives on it, so load_heartbeat_config returns nothing and the whole subsystem is inert on a deployment without one.
Check types¶
| Type | What it does |
|---|---|
file-watch |
Monitors file age and existence |
shell-command |
Runs a command and evaluates a condition (admin-only) |
url-health |
Checks HTTP status codes |
calendar-conflicts |
Detects overlapping calendar events |
task-deadline |
Finds overdue items in TASKS.md |
self-check |
System diagnostics: Claude binary, bwrap, DB connectivity, task failure rate, and a live sandboxed execution test |
shell-command checks run with build_stripped_env(), but arbitrary shell is still arbitrary shell — a non-admin's check returns unhealthy with "shell-command checks are admin-only" rather than running.
The self-check execution test actually spawns a sandboxed claude and asks it to echo a known string, which is the only step that proves the whole path works end to end. It is on by default; set execution_test = false on the check to skip it.
Per-check controls¶
| Setting | Purpose |
|---|---|
interval_minutes |
Run expensive checks less frequently |
cooldown_minutes |
Prevent alert fatigue (minimum time between alerts) |
channel |
Deliver this check's alerts to an explicit surface, overriding the user's routing |
Global settings¶
Under [settings] in HEARTBEAT.md:
| Setting | Purpose |
|---|---|
quiet_hours |
Suppress alerts during off-hours (cross-midnight supported). Global, not per-check |
default_cooldown_minutes |
Cooldown for checks that don't set their own (default 60) |
conversation_token |
Default room for this user's heartbeat alerts |
State is tracked in the heartbeat_state table: last check time, last alert time, last healthy time, last error time, and consecutive error count.
Alerts¶
When a check fails, the alert is routed through surface_for_purpose(config, user_id, "alert") — the user's purpose-keyed routing table, or the check's own channel when it sets one. See per-user delivery routing.
If the resolved channel isn't configured, the alert is skipped with a warning and the consecutive-error count is not incremented — an undeliverable alert is a deployment gap, not a failing check.
The !check command in Talk runs an immediate inline health check (the same diagnostics as self-check, independent of the heartbeat schedule).
Configuration¶
Heartbeat checks are defined in HEARTBEAT.md as markdown with embedded TOML, similar to CRON.md. The scheduler evaluates checks every heartbeat_check_interval (default 60s).