Skip to content

[Bug]: agents.defaults.heartbeat.model Config Ignored #19445

@sbmilburn

Description

@sbmilburn

SUMMARY

The agents.defaults.heartbeat.model configuration is ignored. Heartbeats use the session's active model (or agents.defaults.model.primary) instead of the configured heartbeat model, resulting in unexpected costs when the default model is expensive.


Configuration

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "openrouter/anthropic/claude-sonnet-4.5",
        "fallbacks": [
          "openrouter/meta-llama/llama-3.3-70b-instruct:free",
          "openrouter/anthropic/claude-haiku-4.5"
        ]
      },
      "heartbeat": {
        "model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
      }
    }
  }
}

Steps to reproduce

  1. Configure agents.defaults.heartbeat.model to a different model than agents.defaults.model.primary:

    {
      "agents": {
        "defaults": {
          "model": { "primary": "openrouter/anthropic/claude-sonnet-4.5" },
          "heartbeat": {
            "every": "5m",
            "model": "openrouter/meta-llama/llama-3.3-70b-instruct:free"
          }
        }
      }
    }
  2. Start the gateway and wait for a heartbeat to fire.

  3. Check API logs (OpenRouter dashboard, provider logs, or gateway debug logs).

  4. Observed: Heartbeat uses claude-sonnet-4.5, not the configured free model.

Expected behavior

Heartbeats should use openrouter/meta-llama/llama-3.3-70b-instruct:free

Actual behavior

Heartbeats use openrouter/anthropic/claude-sonnet-4.5 (the default primary model)

OpenClaw version

2026.2.15

Operating system

Ubuntu 24.04

Install method

npm global

Logs, screenshots, and evidence

## Evidence

### OpenRouter Activity Logs (2026-02-16 to 2026-02-17)

Heartbeats fired hourly at the `:57` mark, all using Sonnet instead of the configured free model:

| Timestamp (PST) | Model | Cost | Pattern |
|-----------------|-------|------|---------|
| 02/16 20:57 | claude-sonnet-4.5 | $0.159 | tool_calls → stop |
| 02/16 21:57 | claude-sonnet-4.5 | $0.158 | tool_calls → stop |
| 02/16 22:57 | claude-sonnet-4.5 | $0.158 | tool_calls → stop |
| 02/16 23:57 | claude-sonnet-4.5 | $0.158 | tool_calls → stop |
| 02/17 00:57 | claude-sonnet-4.5 | $0.158 | tool_calls → stop |
| 02/17 01:57 | claude-sonnet-4.5 | $0.158 | tool_calls → stop |
| 02/17 02:57 | claude-sonnet-4.5 | $0.158 | tool_calls → stop |

**Total overnight burn:** ~$1.30 in heartbeats that should have been free.

Each heartbeat consists of two API calls:
1. First call returns `tool_calls` (reading HEARTBEAT.md)
2. Second call returns `stop` (replying HEARTBEAT_OK)

Both calls use the session model, not the configured `heartbeat.model`.

### Raw Log Sample


generation_id,created_at,model_permaslug,cost_total,tokens_prompt,finish_reason
gen-1771282658-yHPh7svS1WXnYyu9Xxee,02/16/2026 22:57:38,anthropic/claude-4.5-sonnet-20250929,0.078777,21064,stop
gen-1771282654-vJvluVL0Y7YZYeCd7Khg,02/16/2026 22:57:34,anthropic/claude-4.5-sonnet-20250929,0.079365,20654,tool_calls
gen-1771279057-hjpIUuKPdwrCxknD36Sx,02/16/2026 21:57:37,anthropic/claude-4.5-sonnet-20250929,0.078777,21064,stop
gen-1771279054-ZTUnVMVoTwBdYBhOr3KW,02/16/2026 21:57:33,anthropic/claude-4.5-sonnet-20250929,0.07959,20654,tool_calls


All calls show `anthropic/claude-4.5-sonnet-20250929` despite `heartbeat.model` being set to the free Llama model.

Impact and severity

  • Cost: Users configuring cheap/free heartbeat models still get charged at primary model rates
  • Silent failure: No warning that heartbeat.model is being ignored
  • Workaround required: Must disable heartbeats entirely (every: "0m") to prevent unexpected charges

Additional information

Related Issues


Suspected Root Cause

Based on the root cause analysis in #9742:

// In src/auto-reply/reply/get-reply.ts (or similar)
const agentCfg = cfg.agents?.defaults;
const heartbeatRaw = agentCfg?.heartbeat?.model?.trim() ?? "";

The heartbeat model may be read but not actually applied to the LLM request. The session's active model (or model.primary) takes precedence.

Alternatively, the model resolution logic may be checking for session-level overrides before falling back to heartbeat config, but the precedence is inverted.


Suggested Fix

  1. Fix model resolution — Ensure heartbeat.model is actually applied when making LLM requests during heartbeat runs.

  2. Add validation warning — If heartbeat.model differs from the session model but isn't being used, log a warning.


Feature Request: Heartbeat Model Fallbacks

Additionally, please consider adding fallback support for heartbeat.model:

{
  "agents": {
    "defaults": {
      "heartbeat": {
        "model": {
          "primary": "openrouter/meta-llama/llama-3.3-70b-instruct:free",
          "fallbacks": [
            "openrouter/anthropic/claude-haiku-4.5"
          ]
        }
      }
    }
  }
}

Rationale: Free tier models are rate-limited and unreliable. Without fallbacks, a failed heartbeat model either:

  • Falls back to the agent's default model (expensive, current buggy behavior)
  • Or fails entirely (if this bug is fixed but no fallbacks exist)

Having explicit heartbeat fallbacks would let users define a cost-controlled chain: free → haiku → fail rather than free → sonnet.

This is related to #17582 - "Feature Request: Support model fallback chain for heartbeat.model"


Workaround

Until fixed, disable heartbeats entirely:

{
  "agents": {
    "defaults": {
      "heartbeat": {
        "every": "0m"
      }
    }
  }
}

Use cron jobs with explicit model settings for any periodic tasks instead.


Environment Details

OpenClaw: 2026.2.15 (3fe22ea)
OS: Linux 6.8.0-100-generic (x64)
Node: v25.6.1
Provider: OpenRouter (openrouter.ai)
Channel: WhatsApp

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions