Back to Runbook
💓 Chapter 4

Heartbeat & Task Tracking

A rotating heartbeat pattern that batches checks on a cheap model, plus Todoist integration so you can glance at your phone and know what your agent is doing.

📖 10 min read📋 2 systems
Part 1

Rotating Heartbeat Pattern

A practical rotating heartbeat pattern

Instead of running separate cron jobs for different periodic checks, I use a single heartbeat that rotates through checks based on how overdue each one is.

The idea is simple. Each check has a cadence (how often it should run), an optional time window (when it's allowed to run), and a record of the last time it ran. On each heartbeat tick, the system runs whichever check is most overdue.

This batches background work, keeps costs flat, and avoids the "everything fires at once" problem.

💡

Key insight: All heartbeat checks run on a very cheap model. If a check finds something that needs work, it spawns the appropriate agent instead of trying to do it inline.

Heartbeat model cost

Use the cheapest model you have access to. Heartbeats run often but do simple checks (read a file, check a condition). No reason to burn premium models here.

Heartbeat Cost Comparison (48 beats/day)
GPT-5 Nano
~$0.005/day(~$0.0001/beat)
Claude Sonnet
~$0.24/day(~$0.005/beat)

That's $0.15/month vs $7.20/month — a 48x difference for work that doesn't need a premium model.

Prompt template

Copy and customize this prompt to have your agent build a rotating heartbeat system:

Prompt — Give this to your agent
Build a rotating heartbeat check system for HEARTBEAT.md:

Create these checks with their cadences:
- Email: every 30 min (9 AM - 9 PM only)
- Calendar: every 2 hours (8 AM - 10 PM only)
- Tasks: every 30 min (anytime)
- Git: every 24 hours (anytime)
- System: every 24 hours (3 AM only)

Create heartbeat-state.json to track last run timestamps.

On each heartbeat:
1. Read state file
2. Calculate which check is most overdue (respect time windows)
3. Run that check
4. Update timestamp in state file
5. Report only if check finds something actionable
6. Return HEARTBEAT_OK if nothing needs attention

Check implementations:
- Email: Check [your email service] for new messages from authorized senders
- Calendar: Check [your calendar] for events in next 24-48h
- Tasks: Check [your task manager] for stalled/blocked work
- Git: Check workspace for uncommitted changes
- System: Check for failed cron jobs and error logs

Adapt these to my specific services and tools.

Example HEARTBEAT.md structure

The resulting HEARTBEAT.md should look something like this:

HEARTBEAT.md
# HEARTBEAT.md

## Cadence-Based Checks

Read `heartbeat-state.json`. Run whichever check is most overdue.

**Cadences:**
- Email: every 30 min (9 AM - 9 PM)
- Calendar: every 2 hours (8 AM - 10 PM)
- Tasks: every 30 min (anytime)
- Git: every 24 hours (anytime)
- System: every 24 hours (3 AM only)

**Process:**
1. Load timestamps from heartbeat-state.json
2. Calculate which check is most overdue (considering time windows)
3. Run that check
4. Update timestamp
5. Report if actionable, otherwise HEARTBEAT_OK

---

## Email Check
Check [your email service] for new messages.
**Report ONLY if:** New email from authorized sender, contains actionable request
**Update:** email timestamp in state file

---

## Calendar Check
Check [your calendar] for upcoming events.
**Report ONLY if:** Event starting in <2 hours, new event since last check
**Update:** calendar timestamp in state file

---

## Task Check
Check [your task manager] for work status.
**Report ONLY if:** Tasks stalled >24h, blocked tasks need attention
**Update:** tasks timestamp in state file

---

## Git Check
Check workspace git status.
**Report ONLY if:** Uncommitted changes exist, unpushed commits found
**Update:** git timestamp in state file

---

## System Check
Check for system issues.
**Report ONLY if:** Failed cron jobs found, recent errors in logs
**Update:** system timestamp in state file

State file

The state file tracks when each check last ran:

heartbeat-state.json
{
  "lastChecks": {
    "email": 1703275200000,
    "calendar": 1703260800000,
    "tasks": 1703270000000,
    "git": 1703250000000,
    "system": 1703240000000
  }
}

Why this pattern works

🔄

Single heartbeat, multiple checks

Simpler than managing separate cron jobs

📊

Spreads load

Checks run when overdue, not all at once

💰

Cost-efficient

One cheap model does all routing

🔍

Debuggable

State file shows exactly when each check last ran

Customization

⏱️ Adjust Cadences

  • • High-priority: 15–30 min
  • • Medium-priority: 1–2 hours
  • • Low-priority: 6–24 hours

🕐 Time Windows

  • • Email/Calendar: waking hours
  • • System checks: overnight
  • • Git: anytime

🔀 Replace Check Types

  • • Email → RSS feeds, webhooks
  • • Calendar → project deadlines
  • • Tasks → CI/CD status
  • • Git → backup status
  • • System → service health

Alternative: Separate cron jobs

If you prefer explicit scheduling over the rotating pattern:

JavaScript — Separate Cron Jobs
// Email check: every 30 min, 9 AM - 9 PM
cron({
  action: "add",
  job: {
    schedule: { kind: "cron", expr: "*/30 9-21 * * *" },
    payload: { kind: "agentTurn", message: "Check email" }
  }
})

// Git check: daily at 3 AM
cron({
  action: "add",
  job: {
    schedule: { kind: "cron", expr: "0 3 * * *" },
    payload: { kind: "agentTurn", message: "Check git status" }
  }
})

Rotating heartbeat is better for frequent checks and cost optimization. Separate cron is better for precise timing.

Part 2

Task Tracking with Todoist

Using Todoist for task visibility

Early on, OpenClaw felt like a black box. I couldn't tell what it was doing, what it had finished, or what was stuck. The logs weren't enough.

I fixed that by wiring up Todoist as the source of truth for task state. Tasks get created when work starts, updated as state changes, assigned to me when human intervention is required, and closed when done. If something fails, it leaves a comment on the task instead of retrying forever.

A lightweight heartbeat reconciles what's open, what's in progress, and what looks stalled. It's not sophisticated, but I can glance at Todoist and know exactly where things stand without digging through logs.

Why task tracking?

OpenClaw can feel like a black box. You don't know what it's doing, what's stuck, or what needs your attention. A task tracking system solves this by making all agent work visible in a tool you already check (Todoist, GitHub Projects, Linear, etc.).

💡

About this approach: This example uses Todoist, but the pattern works with any task manager that has an API. The key is creating visibility into agent state through a tool you can glance at anytime.

Prerequisites

Before giving this prompt to your agent:

1

Todoist account

Free or paid — todoist.com

2

API token

Settings → Integrations → Developer → API Token

3

Projects

Create Backlog/Queue, Active, and optionally Waiting/Blocked projects

4

Credentials directory

Secure storage for API token

bash — Set up credentials
mkdir -p ~/.openclaw/credentials
echo "your-todoist-api-token-here" > ~/.openclaw/credentials/todoist
chmod 700 ~/.openclaw/credentials
chmod 600 ~/.openclaw/credentials/todoist

Prompt template

Copy and customize this prompt to give to your agent:

Prompt — Task tracking system
Build a Todoist-based task tracking system for OpenClaw:

GOAL: Make agent work visible without checking logs. Glance at Todoist
and see exactly what's happening.

STATE MODEL:
- Queue/Backlog: Tasks waiting to start
- Active: Work currently in progress
- Waiting/Assigned to me: Blocked on human input
- Done: Completed

OPERATIONS NEEDED:
1. create_task(title, project) - Add task to queue/backlog
2. move_to_active(task_id) - Start work, move to Active project
3. assign_to_me(task_id, reason) - Mark blocked, assign with comment
4. complete_task(task_id) - Mark done and close
5. add_comment(task_id, status) - Add progress updates to task

RECONCILIATION (runs via heartbeat every 30 min):
- Find Active tasks with no updates >24 hours (stalled)
- List tasks assigned to me (need my attention)
- Report summary if issues found, otherwise HEARTBEAT_OK

TECHNICAL:
- Use Todoist REST API v1: https://api.todoist.com/api/v1/
- API Documentation: https://developer.todoist.com/api/v1/
- Python SDK: https://doist.github.io/todoist-api-python/
- TypeScript SDK: https://doist.github.io/todoist-api-typescript/
- Store API token in ~/.openclaw/credentials/todoist
- Handle API rate limits gracefully

VISIBILITY:
I should open Todoist and immediately understand:
- What the agent is working on right now
- What's waiting for me
- What's stuck
- What's been completed

Adapt this to my Todoist setup.

Expected behavior

Once built, the system should:

Auto-create tasks when the agent starts work
Update task status as work progresses
Assign to you when blocked on input
Add comments with progress updates
Complete tasks when done
Report stalled work via heartbeat reconciliation

Customization

Alternative task managers

  • GitHub Projects: Use GitHub Projects API
  • Linear: Use Linear API
  • Notion: Use Notion database API
  • Asana: Use Asana API

State model variations

  • Simpler: Queue → Active → Done (no waiting state)
  • More complex: Queue → Active → Review → Waiting → Done
  • Kanban-style: To Do → In Progress → Blocked → Done

Reconciliation frequency

  • More aggressive: Every 15 minutes
  • Less aggressive: Every hour
  • Time-windowed: Only during work hours

Troubleshooting

Agent not creating tasks

  • • Verify API token is correct
  • • Check credentials file has raw token (no ENV format)
  • • Ensure projects exist or agent has permission to create them

Tasks stuck in Active

  • • Check reconciliation is running (heartbeat every 30 min)
  • • Look for stalled task reports
  • • Manually move tasks if needed

Rate limit errors

  • • Todoist API has rate limits (consult docs)
  • • Add exponential backoff in error handling
  • • Reduce reconciliation frequency if needed

Alternative approaches

Instead of building from scratch, you can:

  1. Look for inspiration — Browse community skills for task management patterns, but don't install them blindly
  2. Have your agent build it — Use the prompt above and let your agent create the system
  3. Integrate with existing tools — GitHub Issues, Jira, etc.
  4. Build your own state tracking — File-based, database, etc.
⚠️

Note on third-party skills: Be cautious with installing skills directly from others. A poorly written or malicious skill can cause real problems. Treat community skills as inspiration rather than drop-ins. Your agent is capable of building this — use the prompt.

The key is making agent work visible, not the specific tool.