Heartbeat 冷启动 + 持久化 + 补跳机制

问题描述

当前 heartbeatService 的实现存在三个相互关联的缺陷,导致在日常使用中(尤其是开发阶段频繁重启 Alma 的场景)heartbeat 几乎无法按预期触发,并且触发时会无差别终止所有正在执行的 agent 任务。

根因分析

从 API 行为和 /api/heartbeat/status 的返回值推断,start() 使用了 setInterval 而非立即执行,且 lastHeartbeatTime 未持久化。以下用伪代码描述推断出的当前行为:

// 推断的当前行为(伪代码) class HeartbeatService { lastHeartbeatTime = 0; // 纯内存变量,重启归零 start() { this.timer = setInterval( // 无首跳 () => this.tick(), 60 * this.config.intervalMinutes * 1000 ); } } 

缺陷1:无冷启动首跳

setInterval 不会在调用时立即执行回调,而是等待完整的一个 interval 周期后才触发第一次。假设 interval 设为 60 分钟,Alma 启动后需要等整整 60 分钟才会执行第一次 heartbeat。

如果用户在这 60 分钟内重启了 Alma(开发阶段很常见),计时器重置,又要再等 60 分钟。结果是 heartbeat 可能永远不会触发。

缺陷2:lastHeartbeatTime 纯内存,重启归零

lastHeartbeatTime 没有持久化到 SQLite 或 settings JSON。每次 Alma 重启,这个值回到 0。这意味着:

  • 重启后无法判断"距离上次 heartbeat 过了多久"

  • 无法做出"是否需要立即补跳"的决策

  • 日志中也无法追溯 heartbeat 历史

缺陷3:无补跳逻辑

即使检测到距离上次 heartbeat 已经过了多个 interval,当前实现也不会补发。比如 Alma 关闭了 3 小时后重新打开,interval 是 60 分钟,理论上错过了 3 次 heartbeat,但重启后只会静默等待下一个完整 interval。

衍生问题:heartbeat 触发时无差别终止 agent

这个问题跟上面三个缺陷叠加后尤其严重。实测中观察到 heartbeat 触发时会无差别终止正在执行的 agent 任务(多个并行 agent 被同时中断),导致任务执行不完整。如果 heartbeat 的触发时机不可预测(因为缺陷1-3),用户无法合理规避这个中断。

由于缺陷2(无持久化),重启后 agent 无法知道自己被中断过,也无法从断点恢复。

建议的修复方案

Fix 1:启动时立即执行首跳(一行代码)

start() { this.tick(); // ← 新增:立即执行一次 this.timer = setInterval( () => this.tick(), 60 * this.config.intervalMinutes * 1000 ); } 

Fix 2:持久化 lastHeartbeatTime(约 5 行)

lastHeartbeatTime 写入 app_settings 表或独立的 JSON 文件。每次 tick() 执行后更新持久化值,启动时从持久化存储读取。

start() { this.lastHeartbeatTime = this.loadFromStorage() || 0; // ... } tick() { // ... 执行 heartbeat 逻辑 this.lastHeartbeatTime = Date.now(); this.saveToStorage(this.lastHeartbeatTime); } 

Fix 3:启动时补跳判断(约 5 行)

start() { const last = this.loadFromStorage() || 0; const elapsed = Date.now() - last; const intervalMs = 60 * this.config.intervalMinutes * 1000; if (elapsed >= intervalMs) { this.tick(); // 补跳 } else { // 只等剩余时间,而不是完整 interval setTimeout(() => { this.tick(); this.timer = setInterval(() => this.tick(), intervalMs); }, intervalMs - elapsed); return; } this.timer = setInterval(() => this.tick(), intervalMs); } 

Fix 4(建议但非必须):heartbeat 触发时的 graceful shutdown

当前 heartbeat 触发时直接终止所有 agent。建议改为:

  1. 检查是否有 agent 正在执行任务

  2. 如果有,等待当前任务完成(或设一个超时上限,比如 30 秒)

  3. 任务完成或超时后再执行 heartbeat 逻辑

  4. 如果无法等待,至少让 agent 写一个 checkpoint,以便恢复后能从断点继续

这一条改动量较大,可以作为后续优化。

优先级建议

修复项 改动量 优先级 理由

Fix 1(首跳)

1 行

P0

不修的话 heartbeat 在频繁重启场景下几乎永远不触发

Fix 2(持久化)

~5 行

P1

跟 Fix 3 配合才有意义

Fix 3(补跳)

~10 行

P1

解决长时间关闭后重启的 gap

Fix 4(graceful)

~30 行

P2

产品体验问题,当前可用 workaround 是避开 heartbeat 时段

环境信息

  • Alma 版本:v0.0.738

  • 操作系统:Windows 11

  • 使用场景:通过 Discord bridge 运行多 agent 长任务

  • 复现频率:每次重启 Alma 后 heartbeat 行为异常,100% 可复现

Please authenticate to join the conversation.

Upvoters
Status

In Review

Board
💡

Feature Request

Date

About 4 hours ago

Author

karlamo

Subscribe to post

Get notified by email when there are changes.