#17583: feat(heartbeat): add agentId to route default heartbeat to a specific agent
docs
gateway
stale
size: S
Cluster:
Heartbeat Management Improvements
## Problem
To run heartbeats as a non-default agent, users currently have to either:
1. **Disable the main heartbeat** (`every: "0m"`) and duplicate the entire heartbeat config into the target agent's `agents.list[].heartbeat` block
2. **Make the target agent the default** — which changes routing for everything else too
This is unnecessarily complex for a common use case: "I want my heartbeat to run as my `ops` agent, not my `main` agent."
## Solution
Add `agents.defaults.heartbeat.agentId` — a single field that routes the default heartbeat to a specific agent:
```json5
{
agents: {
defaults: {
heartbeat: {
every: "30m",
agentId: "ops", // ← just add this
target: "last",
},
},
list: [
{ id: "main", default: true },
{ id: "ops" },
],
},
}
```
**Before:** disable main heartbeat + duplicate config into per-agent block
**After:** one field, same defaults config, different agent
### Precedence
- If any `agents.list[]` entry has a `heartbeat` block → `agentId` is ignored (per-agent blocks take full control, same as today)
- If no per-agent heartbeat blocks exist → heartbeat runs as `agentId` (or the default agent if `agentId` is not set)
This is fully backward compatible — the behavior when `agentId` is not set is identical to today.
## Implementation
- **`src/config/types.agent-defaults.ts`** — Added `agentId?: string` to heartbeat config type with JSDoc
- **`src/infra/heartbeat-runner.ts`** — Updated `resolveHeartbeatAgents()` and `isHeartbeatEnabledForAgent()` to respect `agentId`
- **`docs/gateway/heartbeat.md`** — Documented `agentId` field, added routing section, updated config example and field notes
- **`src/infra/heartbeat-runner.agent-routing.test.ts`** — 4 tests covering: default routing, agentId routing, per-agent precedence, single-agent setup
## Testing
- [x] 4 new tests for agent routing (all passing)
- [x] 98 existing heartbeat tests still pass
- [x] Build passes
- [x] Lint passes
- [x] Backward compatible
## Notes
- AI-assisted PR (Claude) — fully tested
- This is a feature proposal — happy to adjust naming or behavior based on maintainer feedback
- Discussion: https://github.com/openclaw/openclaw/discussions/17553
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds `agents.defaults.heartbeat.agentId` field to route the default heartbeat to a specific agent without needing per-agent heartbeat blocks. This simplifies configuration for the common use case of running heartbeats as a non-default agent (e.g., running as "ops" instead of "main").
**Changes:**
- Added `agentId?: string` to the heartbeat config type with comprehensive JSDoc explaining behavior and precedence rules (src/config/types.agent-defaults.ts:183)
- Updated `isHeartbeatEnabledForAgent()` to check `agentId` when no per-agent heartbeat blocks exist (src/infra/heartbeat-runner.ts:134-136)
- Updated `resolveHeartbeatAgents()` to use `agentId` as fallback before defaulting to the default agent (src/infra/heartbeat-runner.ts:216-218)
- Documented the feature with examples and precedence rules in docs/gateway/heartbeat.md
- Added 4 test cases covering default routing, `agentId` routing, per-agent precedence, and single-agent setup
**Backward compatibility:** Fully backward compatible - when `agentId` is not set, behavior is identical to current implementation.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is simple, well-tested (4 new tests covering key scenarios), fully backward compatible, and follows existing patterns in the codebase. The change only affects heartbeat routing logic when the new optional field is set, with clear precedence rules. All existing tests pass according to the PR description.
- No files require special attention
<sub>Last reviewed commit: 1335cb8</sub>
<!-- greptile_other_comments_section -->
<sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#7350: fix(cron): pass agentId and AccountId through heartbeat chain for m...
by codeslayer44 · 2026-02-02
75.9%
#20948: fix: propagate accountId from heartbeat delivery context to agent run
by odrobnik · 2026-02-19
74.6%
#12786: fix: drop heartbeat runs that arrive while another run is active
by mcaxtr · 2026-02-09
73.1%
#19745: fix(heartbeat): enforce interval check regardless of trigger source
by misterdas · 2026-02-18
72.9%
#20563: fix: skip default HEARTBEAT_OK instructions when custom prompt is set
by clawalpha · 2026-02-19
72.4%
#18962: feat: add priority preemption — heartbeat lane separation
by rsepulveda23 · 2026-02-17
72.2%
#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
by namabile · 2026-02-17
72.2%
#13524: feat: conditional bootstrap file loading for heartbeat vs DM sessions
by tarun131313 · 2026-02-10
71.0%
#23656: fix(routing): trust binding agentId even when not in agents.list
by SleuthCo · 2026-02-22
70.9%
#15982: fix: pass agentId to resolveSessionFilePath in reply flow (NX-003)
by automagik-genie · 2026-02-14
70.7%