Heartbeat Token Cost Analysis

Deep analysis of OpenClaw's cron heartbeat mechanism and its token cost implications.

Source

src/cron/service/timer.ts (877 lines)

What is a Heartbeat?

A heartbeat is a full context sent to the LLM every 600 seconds (10 minutes) expecting a simple "HEARTBEAT_OK" response. This keeps long-running cron jobs alive and responsive.

export const DEFAULT_JOB_TIMEOUT_MS = 10 * 60_000; // 10 minutes

Cost Model

Token Consumption Per Call

ScenarioInput TokensOutput TokensTotal Cost/Call
Low (minimal history)~10,000~800~$0.045
Medium (typical session)~50,000~1,000~$0.165
High (max context)~150,000~2,000~$0.48

Pricing assumptions (Claude Opus 4.6):

  • Input: $3/M tokens
  • Output: $15/M tokens

Daily Cost Projections

At 600-second (10-minute) intervals = 144 heartbeats/day

ScenarioTokens/DayDaily Cost
Low10,800 × 144 = 1,555,200$6.48
Medium50,800 × 144 = 7,315,200$24.15
High152,000 × 144 = 21,888,000$86.40

Monthly Projections (30 days)

ScenarioMonthly Cost
Low~$194
Medium~$725
High~$2,592

Error Backoff Strategy

When heartbeat errors occur, OpenClaw applies exponential backoff:

// From src/cron/service/timer.ts
const ERROR_BACKOFF_SCHEDULE_MS = [
  30_000,      // 1st error  →  30 seconds
  60_000,      // 2nd error  →   1 minute
  5 * 60_000,  // 3rd error  →   5 minutes
  15 * 60_000, // 4th error  →  15 minutes
  60 * 60_000, // 5th+ error →  60 minutes
];

function errorBackoffMs(consecutiveErrors: number): number {
  const idx = Math.min(consecutiveErrors - 1, ERROR_BACKOFF_SCHEDULE_MS.length - 1);
  return ERROR_BACKOFF_SCHEDULE_MS[Math.max(0, idx)];
}

MAX_SCHEDULE_ERRORS = 3 before job is disabled.

Timeout Configuration

export const DEFAULT_JOB_TIMEOUT_MS = 10 * 60_000; // 10 minutes

function resolveCronJobTimeoutMs(job: CronJob): number | undefined {
  const configuredTimeoutMs =
    job.payload.kind === "agentTurn" && typeof job.payload.timeoutSeconds === "number"
      ? Math.floor(job.payload.timeoutSeconds * 1_000)
      : undefined;

  if (configuredTimeoutMs === undefined) {
    return DEFAULT_JOB_TIMEOUT_MS;
  }

  return configuredTimeoutMs <= 0 ? undefined : configuredTimeoutMs;
}

Timeout per job execution: 10 minutes (600,000ms)

Optimization Recommendations

1. Increase Heartbeat Interval

Current: 600 seconds (10 minutes) Proposed: 1800-3600 seconds (30-60 minutes)

Savings: 3-6x reduction in daily costs

IntervalHeartbeats/DayDaily Cost (High)
10 min (current)144$86.40
30 min48$28.80
60 min24$14.40

2. Use Lightweight Health Check

Replace full context with minimal status check:

  • Send only: job metadata + last run status
  • Expected response: simple "OK" or status enum
  • Token savings: 95%+ reduction

Estimated cost with lightweight check:

  • Input: ~500 tokens (metadata only)
  • Output: ~100 tokens
  • Cost/call: ~$0.003
  • Daily cost: ~$0.43 (vs. $86.40)

3. Conditional Heartbeat

Only send heartbeat when:

  • Job has pending work
  • Last run was > 1 hour ago
  • User explicitly configured heartbeat: true

Savings: 50-80% reduction depending on job activity patterns

4. Context Pruning for Heartbeats

Apply aggressive compaction before heartbeat:

  • Keep only: last 5-10 messages
  • Summary of older history
  • Token savings: 60-80% reduction

Trade-offs

ApproachToken SavingsImplementation ComplexityRisk
Increase intervalHigh (3-6x)LowJob unresponsiveness
Lightweight checkVery High (20x)MediumLoss of context awareness
ConditionalMedium (2-5x)MediumMissed edge cases
Context pruningMedium-High (5-10x)LowReduced context for responses
  1. Increase interval to 30 minutes (immediate 3x savings, minimal risk)
  2. Add lightweight check between full heartbeats (check every 5 min, full heartbeat every 30 min)
  3. Apply context pruning to full heartbeats (keep last 10 messages + summary)

Expected savings: 80-90% cost reduction with acceptable trade-offs

Cross-References