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
-
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"
}
}
}
}
-
Start the gateway and wait for a heartbeat to fire.
-
Check API logs (OpenRouter dashboard, provider logs, or gateway debug logs).
-
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
-
Fix model resolution — Ensure heartbeat.model is actually applied when making LLM requests during heartbeat runs.
-
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
SUMMARY
The
agents.defaults.heartbeat.modelconfiguration is ignored. Heartbeats use the session's active model (oragents.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
Configure
agents.defaults.heartbeat.modelto a different model thanagents.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" } } } }Start the gateway and wait for a heartbeat to fire.
Check API logs (OpenRouter dashboard, provider logs, or gateway debug logs).
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:freeActual 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
Impact and severity
heartbeat.modelis being ignoredevery: "0m") to prevent unexpected chargesAdditional information
Related Issues
Per-agent heartbeat.model config is ignored #9742 - "Per-agent heartbeat.model config is ignored" — Similar issue but for per-agent config (
agents.list[].heartbeat.model). That issue notes thatagents.defaults.heartbeat.modelshould work, but our testing shows it doesn't.[Bug]: agents.defaults.subagents.model config ignored in cron isolated sessions #11461 - "agents.defaults.subagents.model config ignored in cron isolated sessions" — Same pattern: model configs in
agents.defaults.*are ignored in various contexts.Suspected Root Cause
Based on the root cause analysis in #9742:
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
Fix model resolution — Ensure
heartbeat.modelis actually applied when making LLM requests during heartbeat runs.Add validation warning — If
heartbeat.modeldiffers 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:
Having explicit heartbeat fallbacks would let users define a cost-controlled chain:
free → haiku → failrather thanfree → 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
modelsettings for any periodic tasks instead.Environment Details