背景
我用 OpenClaw 跑了一套 AI agent 系统。主 agent(未然)用 Claude Opus,每 15 分钟触发一次心跳巡检。某天看 GoodVision 账单吓了一跳:
| 日期 | 花费 |
|---|---|
| 3⁄5 | $184 |
| 3⁄6 | $303 |
| 3⁄7 | $298 |
| 3⁄8 | $100 |
| 优化后 3⁄9 | $69 |
| 优化后 3⁄10 | $27 |
三天烧了 $700 多,96% 都花在 Opus 的 input tokens 上。
根因分析
1. 心跳和对话共享 session
OpenClaw 的心跳设计是跑在 main session 里的。这意味着:
- 心跳每 15 分钟往主 session 塞一轮对话(含 tool calls、HEARTBEAT.md 读取等)
- Telegram 的日常对话也在同一个 session 累积
- session 文件膨胀到 8.9MB
- 每次 LLM 调用都要发送完整上下文 → Opus $5/MTok input,一天下来几百刀
2. /new 对心跳无效
用户用 /new 只能重置 Telegram DM 的 session,心跳绑定在系统的 agent:main:main session 上,不受影响。所以 session 一直在膨胀。
3. 心跳本身不需要 Opus
心跳干的事就是:读 HEARTBEAT.md → Jira checkin → 看评论 → 判断是否拾取任务。这些用 Sonnet 甚至纯脚本就够了。
优化方案
第一层:脚本预检替代 LLM 心跳
核心思路:cron 脚本做零成本巡检,只在发现异常时唤起 LLM。
# heartbeat-precheck.sh — 每 30 分钟跑一次
cron 脚本检查:
1. watchdog 日志有无 CRASH LOOP
2. jira-cli checkin — 有无新任务/新评论
3. 积分余额变化
4. 股票异动 (>5%)
# 全部正常 → exit 0(零成本)
# 发现异常 → openclaw system event --mode now
关键改动:
- 禁用 OpenClaw 内置心跳
- crontab
*/30跑预检脚本 - 脚本里自动关闭 boss 已确认的 review 任务(纯 API 操作,不需要 LLM)
- todo 任务按优先级排序后传给 LLM,减少 LLM 自己再查一遍
第二层:心跳配置优化
当 LLM 被唤起时,也做了优化:
"heartbeat": {
"every": "1h",
"model": "goodvision/claude-sonnet-4-6",
"lightContext": true,
"session": "heartbeat-main"
}
model: sonnet— 心跳用便宜模型(\(0.6/MTok vs \)5/MTok)lightContext: true— 心跳不加载完整 session 历史,只注入 HEARTBEAT.mdsession: heartbeat-main— 心跳独立 session,不污染 Telegram 对话
第三层:Jira Webhook 实时触发
脚本预检有最多 30 分钟延迟。对于 boss 的操作,需要实时响应:
// server.js — Jira 后端
function triggerSystemEvent(text) {
// 调用 OpenClaw /hooks/wake 注入 system event
const data = JSON.stringify({ text, mode: 'now' });
http.request({
hostname: 'host.docker.internal',
port: 18789,
path: '/hooks/wake',
// ...
});
}
// Boss 创建任务 → 立即唤起
if (creator === 'boss') {
triggerSystemEvent(`Kiyor 创建了新任务 #${id}: "${title}"`);
}
// Boss 发评论 → 立即唤起
if (author === 'boss') {
triggerSystemEvent(`Kiyor 在 #${taskId} 发了新评论: "${content}"`);
}
// Agent 评论 → 不触发(零成本)
第四层:Session 膨胀防护
"session": {
"maintenance": {
"mode": "enforce",
"pruneAfter": "7d",
"maxEntries": 200,
"rotateBytes": "10mb"
}
}
加上每天 4:30 AM 的 session 大小检查脚本,超 5MB 自动开 Jira ticket 上报(去重 + 重复检测追加评论)。
架构对比
@startuml
!theme plain
skinparam backgroundColor #FEFEFE
title 优化前
participant "Cron\n(15min)" as C
participant "OpenClaw\nHeartbeat" as H
participant "Opus LLM\n($5/MTok)" as L
participant "Jira API" as J
C -> H : 触发心跳
H -> L : 发送完整 session\n(8.9MB 上下文)
L -> J : checkin
J --> L : 无新任务
L --> H : HEARTBEAT_OK
note right : 每次 ~$1\n一天 96 次 = ~$100-300
@enduml
@startuml
!theme plain
skinparam backgroundColor #FEFEFE
title 优化后
participant "Cron\n(30min)" as C
participant "预检脚本\n(零成本)" as S
participant "Jira API" as J
participant "Sonnet LLM\n($0.6/MTok)" as L
participant "Jira Webhook" as W
== 定时巡检 ==
C -> S : 执行脚本
S -> J : jira-cli checkin
J --> S : 无异常
S -> S : exit 0\n(不唤起 LLM)
== Boss 操作 ==
W -> L : triggerSystemEvent\n(lightContext)
L -> J : 处理任务
note right : 仅有事才花钱\n一天 5-10 次
@enduml
效果
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 日均 LLM 心跳调用 | ~96 次 (Opus) | 5-10 次 (Sonnet) |
| 日均花费 | $100-300 | $20-30 |
| 心跳响应延迟 | 固定 15min | Boss 操作实时,其余 30min |
| Session 膨胀 | 无限增长 | enforce 自动清理 + 监控告警 |
| 心跳模型 | Opus ($5/MTok) | Sonnet ($0.6/MTok) |
踩过的坑
心跳和对话共享 main session — 这是 OpenClaw 的设计意图(”Heartbeat runs periodic agent turns in the main session”),但会导致上下文无限膨胀。用
session字段可以分离。/new不影响心跳 —/new只重置 Telegram DM session,心跳绑在agent:main:main。Docker 容器里没有
openclawCLI — Jira 后端跑在 Docker 里,最初想用child_process.exec('openclaw system event ...'),但容器里没装 CLI。改用 HTTP 调用/hooks/wake。lightContext: true是关键 — 即使换了便宜模型,如果每次都送完整上下文,token 量还是很大。lightContext 让心跳只带 HEARTBEAT.md,input tokens 从几十万降到几千。去重要用本地状态文件 — 纯靠 API 查询去重不够可靠(网络延迟、查询条件限制),加一个 JSON 文件记录
session → ticket ID映射更稳。
总结
核心原则:能不调 LLM 就不调,必须调时用最便宜的模型和最小的上下文。
这套方案适用于任何跑定时巡检的 AI agent 系统——先用脚本做预检过滤,只在真正需要判断力的时候才唤起大模型。90% 的心跳是 “一切正常”,不需要花 $1 让 Opus 告诉你这件事。