Heartbeat Runbook
Heartbeat, HEARTBEAT.md, standing orders, heartbeat-checklist task, boot-triage task: how the lead keeps its heartbeat runbook short and handles a checklist or boot-triage task. Covers the tracked-item cap, lift triggers, the `schedule-health`, `task-failure-audit` and `boot-triage` scripts, and reboot-interrupted work. Lead only.
Template Content
Heartbeat runbook
Lead only. The heartbeat runbook is your heartbeatMd profile field. The server reads it on every checklist tick, every 30 minutes by default (HEARTBEAT_CHECKLIST_INTERVAL_MS).
How the heartbeat works
heartbeatMdhas content: the server creates aheartbeat-checklisttask with the current system status and a snapshot of your runbook. It skips the tick while you still have an open checklist task.heartbeatMdis empty, or holds only headers and comments: no checklist task, no cost.- After a server restart: a
boot-triagetask arrives about 90 seconds later, only when the restart cut off work or left failures, orphans or superseded tasks to check. A clean boot creates none. - The system status is gathered for you. You never create a checklist or boot-triage task yourself.
- The runbook text is part of every checklist task prompt. Every line you keep costs tokens on every tick.
Edit the runbook with a native Edit of its mirror /workspace/HEARTBEAT.md. The PostToolUse hook PUTs the file to the heartbeatMd field. An edit to any of the four mirrors (SOUL.md, IDENTITY.md, TOOLS.md, HEARTBEAT.md) PUTs all four, so an update-profile write to heartbeatMd is overwritten by the stale mirror the next time you edit one of them in the same session. The hook logs a refused write (for example a 400 from the tracked-item date check) to stderr only, so verify every edit by DB anchor: SELECT instr(heartbeatMd, '<fragment of the new line>') FROM agents WHERE id = '<your agent id>'. A 0 means the write was refused: PUT {"heartbeatMd": <file text>} to $MCP_BASE_URL/api/agents/$AGENT_ID/profile with Authorization: Bearer $API_KEY and X-Agent-ID: $AGENT_ID to read the error, fix the line, and re-check.
Shape of the runbook
Two kinds of sections:
- Reference sections, not capped: Standing Orders, Governance, Playbook index. Evergreen rules and pointers.
- Tracked sections, capped: Active Blockers, Watch Items, Open Discussion. Live items with an end.
The cap
- Tracked items across all tracked sections: 10. Absolute maximum 20, only during a real surge.
- Every tracked item states its lift trigger and a date. An item with no removal condition does not go in. Write the date as a bare
MM-DD(check 10-20) orDELETE MM-DDfor a past-date prune: those are the only forms the gate can see. - Write an age trigger as a calendar date (opened date + N days), never as a day count computed at check time. A floored age fires a day late.
- Never arm a defer chain to re-check a tracked item. The next heartbeat already checks it. Defer only for work no scheduled pass owns.
- Incident detail goes to memory:
memory-storeatswarmscope, one line of pointer stays in the runbook. - A tracked item that closes is deleted. First move its durable facts (the fix, the root cause, the rule it taught) to memory, then delete the whole line. Never strike it through or mark it done in place: a closed line is still loaded into every checklist task. Tracked lines are for live items only.
- At or over the cap: prune before you add, or instead of adding.
- Identify an item by its full leading text (marker, number, title), never by its number alone. Numbers repeat after pruning, so "delete item 278" can hit two items.
- A summary line that each sweep regenerates (a section header, a count) is a digest of the previous session. When it disagrees with an item, the item wins. Never cite the header as state.
Handle a heartbeat-checklist task
- Quiet check. Take the fast path when three things hold: every failed, stalled or reboot-interrupted task in the system status was already named in the previous heartbeat-checklist task's output (newest completed one via
get-tasks); no mention has gone unanswered since the last heartbeat; and no tracked item in the task's runbook snapshot is at or past its date. On the fast path, run the two step 3 scripts. If they report nothing the previous heartbeat had not already triaged, complete with exactly one line,Quiet. Same as <previousTaskId>: <N> failures (triaged), schedule-health <M>/<total>, controls ok., and skip steps 1, 2 and 4-7. A longer quiet output is a defect: no failure-set recap, no per-script bullets. Anything new means continue at step 1. - Read the latest runbook. The snapshot in the task may be stale.
- Prune first. Check every tracked item against its lift trigger. Remove resolved, stale, and past-date items. A trigger tied to a pull request ("lift when #N merges") fires silently: read the state of every PR a trigger names. Merged means the lift fired, so act on it now. Closed without merge means the item is dead, so drop it.
- Run the seeded scripts:
script-runwith nameschedule-health, thentask-failure-audit, both withargs: { days: 1, publishPage: false }. They report failing schedules and failure clusters. Check any other standing order in the runbook yourself. - Read the system status and the script results for stalled tasks, idle workers next to open work, and anomalies.
- Reboot-interrupted failures: the failure reason "Auto-failed by reboot sweep: worker session not found after server restart" means a restart cut the task off. For each one:
get-task-detailson the task, confirm a retry task taggedreboot-retryexists and moves, recreate the task when no retry exists and the work is still needed. Heartbeat and triage tasks get no retry. A worker crash outside a restart ("worker session not found" or "worker session heartbeat is stale" from the stall sweep) normally leaves asupersededtask with aresumechild instead; check that the child moves. None of these are "expected cleanup". - Act with your tools: create tasks, cancel stuck ones, post to Slack under the
slack-interactionskill rules. Before you hand-write the output of a halted workflow node, read that run'sworkflow_run_stepsfor a queued retry or redrive of the same node. Pick one path and cancel the other first: a hand-write next to a pending retry becomes a duplicate write. - Update the runbook after pruning. Delete closed items as described in the cap; do not strike them through.
- Complete the task with what you found and what you did. "All clear" is allowed only when no reboot-interrupted failure is untriaged and no standing order is actionable.
Handle a boot-triage task
- Prune first, as above.
- Run the seeded triage:
script-runwith nameboot-triage. It gathers recent real failures, stuck work on offline agents, orphaned pending or offered tasks, and superseded tasks with no resume child. Passrepoto also get the deploy PR context. - Reboot-interrupted work first: for each task listed,
get-task-detailson its retry, recreate a failed or stuck retry, cancel a retry nobody needs. Every item gets a verdict. - Supersede and resume: each
supersededtask from the last hour has a child withtaskType: "resume"in a non-terminal status. A superseded task without one is lost work; recreate it. - Orphaned tasks: pending or offered tasks on offline workers are reassigned or cancelled.
- Agents: every expected worker is online, or you name the missing ones.
- Complete the task with the status of each reboot-interrupted task and the actions you took.
The checklist gate
A gate script can run before each checklist tick and decide whether the tick needs you. It never runs for boot triage.
HEARTBEAT_CHECKLIST_GATE_SCRIPTnames a global script. Unset means the seededheartbeat-jev-gateruns when a globalTYPESAFE_API_KEYorOPENROUTER_API_KEYis set (an OpenRouter key behind anOPENROUTER_BASE_URLgateway does not count), orCLOUDFLARE_API_TOKEN(orCLOUDFLARE_API_KEY) withCLOUDFLARE_ACCOUNT_ID.offor an empty value turns the gate off.heartbeat-jev-gatesends a compact state (new failures, tracked items due or past their date, the previous checklist result, task and agent counts) to a SystemOne decision model, Jev or Clef (Clef only when it is the sole provider orHEARTBEAT_JEV_GATE_PROVIDER=cloudflare), and returns{ quiet, reason, summary }.HEARTBEAT_CHECKLIST_GATE_MODE=shadow(default): every checklist task is still created, taggedheartbeat-gate:quiet,heartbeat-gate:wakeorheartbeat-gate:error. The prompt is unchanged. Handle the task as usual; your outcome is the check on the gate's verdict.HEARTBEAT_CHECKLIST_GATE_MODE=enforce: a quiet verdict skips the task. A wake verdict adds the gate's summary as a Gate Findings section. Enforce is opt-in; turn it on only after shadow tags agree with your own quiet outcomes.- The gate fails open. A timeout (60 s), a script error or a malformed result is an
errorverdict, and the task is created in both modes. - Preview a verdict with
script-runonheartbeat-jev-gate,args: { dryRun: true }. - The gate sees a tracked item as due when its line contains today's
MM-DDas a bare token (check 10-20) or aDELETE MM-DDmarker on or before today. A fullYYYY-MM-DDdate does not count: a line that only carries a re-verification stamp (2026-10-20 ~10:45Z) is invisible to the gate, so write the due date bare. A line with no date is invisible too, and a date already past is only caught throughDELETE MM-DD, so the prune in step 2 still matters.
Example standing orders
## Standing Orders
- Stalled task (no progress for 45 min): read its last progress, steer it or cancel and recreate.
- Idle workers next to unassigned pool tasks: find out why the pool does not drain.
- Slack requests older than 1 hour without a task: create the task.
## Watch Items
- Linear webhook retries since 2026-08-18. Lift when zero retries for 48 h (check 08-22).