每天 21:00 准点发报告、每 15 分钟执行一次”工作循环”——我的自主 Agent 看起来忙得不可开交。但有人问了一句:”怎么每次报告的内容都差不多?” 一查之下发现:它从 7/27 起就在空转,5 天产出了零。 这篇记录完整的 8 处断链诊断与修复过程,附可直接抄的检查清单。


1. 缘起:一个让人起疑的问题

我搭了一套名为 ActiveAgent 的三向 Agent 互联运行时(连接教育 Agent、股票 MCP 网关和 Hermes),它有两个核心承诺:

  • 每 15 分钟一次 tick:自动扫描 3 个项目 × 9 个维度,发现问题就修复
  • 每天 21:00 报告:汇总当天干了什么、进度如何

某天我翻看连续几天的每日报告,越看越不对劲:

“怎么每次报告的内容都差不多呢?教案进度始终只有 1 个评审,自主扫描都是那几个数字。”

直觉告诉我它可能没在正常工作。于是开始排查——这一查,牵出一串**”看起来活着、实际上空转”**的连环断链。

2. 核心方法:8 处断链的定位与修复

八处断链

第一步:确认”它真的没干活”

先看数据全景:

组件 表面状态 实际状态
Bridge (localhost:8901) ✅ 运行中 0.3.0 活着,但没被正确使用
Education Agent ✅ healthy v2.9.0 正常
Stock Agent ❌ unreachable 假故障(配置错误)
每日报告 cron ✅ 准点跑 内容停滞
核心循环 cron (15min) ✅ 在跑 产出为零

再看关键进度文件 review_progress.json

  • 已生成教案:57 个(7/24-26 批量生成)
  • 已评审:1 个gk_it_01
  • 最后更新时间:7 月 27 日(已停滞 5 天)

autonomy_history.json 里记录的是 31 个 静态代码扫描 findings 的循环播报——print_in_prodbare_excepttodo 这些正则扫描结果。代码没改,结果当然永远一样。

断链 ①:扫描发现了问题,却永远只”报告”不”修复”

这是空转的总开关_scan_review_quality 明明发现了「评审覆盖率低:1/57」,但它生成的 finding 类型是 "action": "report",而不是可执行的 review_lesson

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修复前:发现低覆盖率,却只生成报告型 finding
findings.append({
"id": "low-review-coverage",
"action": "report", # ❌ 只报告,不触发评审
...
})

# 修复后:产出可执行的评审任务
for lid in ["gk_pm_01", "gk_pm_02", "gk_pm_03"]:
findings.append({
"id": f"review-{lid}",
"action": "review_lesson", # ✅ tick 会真的去执行
"target": lid,
})

一个 finding 只有两种下场:被执行,或被打包报告。 如果所有扫描器都只产出 report,那这个自主系统就是个”复读机”。

断链 ②:报告型问题无限刷屏

pick_next_action 每 15 分钟挑一个 finding 报告一遍,31 个轮完从头再来。昨天的 history 完整记录了这个空转:

1
2
3
4
5
6
7
08:46 → stock_tool_too_large_agent_executor.ts (report)
09:00 → low-review-coverage (report)
09:15 → bare_except-app.py-1600 (report)
...
14:00 → stock_index_too_large (report) ← 第二轮循环开始
...
19:45 → stock_tool_too_large_agent_executor.ts (report) ← 又回来了

修复:可执行行动优先 + 报告型 24h 冷却

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def pick_next_action(findings, history):
# 1) 可执行行动优先(generate_lesson / review_lesson / regenerate_lesson)
actionable = [f for f in findings
if f.get("action") in ("generate_lesson", "review_lesson", "regenerate_lesson")]
recent_actions = {h.get("finding_id") for h in history[-10:] if h.get("finding_id")}
for f in actionable:
if f["id"] not in recent_actions:
return f

# 2) 报告型问题:24h 冷却,同一问题一天只报告一次
for f in findings:
if f.get("action") != "report":
continue
last = last_handled.get(f["id"])
if last is None or (now - last).total_seconds() > 86400:
return f
return None # 无事可做就安静,而不是硬找事做

断链 ③:预算死锁——单次成本 > 限额,永远执行不了

review_lesson 一次评审要消耗 6 次 LLM 调用(5 角色评审 + 1 次改进),但预算配置是:

1
2
3
4
5
6
7
MAX_PER_HOUR = 4    # ❌ 修复前:每小时最多 4 次
MAX_PER_DAY = 20
COST_PER_ACTION = {
"generate_lesson": 1,
"review_lesson": 6, # 单次成本 6 > 小时限额 4 → 永远被拒
"regenerate_lesson": 1,
}

成本上限必须高于单次操作成本,这是预算系统的第一性原理。修复后:

1
2
MAX_PER_HOUR = 8    # ✅ 8 > 6,评审终于能执行
MAX_PER_DAY = 24

断链 ④:相对路径 + 错误 cwd = 找不到文件

评审管线用子进程执行,但子进程的 cwd 是 active-agent 目录,而 _load_html 用的是相对路径 ./data/lesson_plans/——文件在 EDU_BASE 下,当然找不到。修复:subprocess.run(..., cwd=EDU_BASE)

断链 ⑤:成功也不回写进度——数字永远不变

就算评审真跑成功了,也没有任何代码更新 review_progress.json进度文件自 7/27 起就没有写入者。 新增 _update_review_progress() / _update_generate_progress(),在成功路径上回写。

断链 ⑥:失败行动写进 history = 永不重试

失败的评审被记入 history → 被 recent_actions 排除 → 下次 tick 不再选它。失败/限流的行动不应污染”已处理”记录,否则一次网络抖动就让任务永远搁浅。

断链 ⑦:Bridge 自愈路径指向不存在的文件

两个脚本的 ensure_bridge_alive() 引用的路径是 /home/admin/.hermes/projects/active-agent/bridge.py(不存在),实际在 /home/admin/projects/active-agent/bridge.py自愈脚本的路径错了 = Bridge 挂了永远起不来。

断链 ⑧:健康检查用假 token

integration.py 里 stock 的 token 是截断占位符 mcp_b5...bab9,导致 stock 永远 unreachable。占位符混进生产配置,是最隐蔽的一种”假状态”。

3. 踩坑记录:什么情况下会再犯

这次诊断最有价值的产出,不是修好了 ActiveAgent,而是提炼出判断自主系统是否空转的检查清单

空转检查清单

  1. 看增量,不看存量:报告里”今天新增了什么/和上次差多少”,比”总量是多少”诚实得多。空转系统最典型的特征就是数字永远不变
  2. 报告 ≠ 干活:一个 finding 只有”被执行”才算闭环。数一数你的扫描器,有多少产出的是 report 型、多少产出可执行任务?
  3. 预算上限 > 单次成本:这是数学问题,不是调参问题。单次 6、上限 4,就是永远死锁。
  4. 失败不进”已处理”:失败/限流应该留在待办池里等重试,而不是从视野里消失。
  5. 路径和 token 要可校验:自愈脚本指向不存在的路径、健康检查用占位符 token——这类错误不报错,只是”永远不正常”,最难发现。

什么时候这套方法不适用:如果 Agent 的定位就是”只扫描不修复”的审计工具(比如 CI 静态检查),那么 report 型 finding 是合理的。判断标准是职责:承诺了”自主修复”,就必须有可执行闭环;承诺了”只报告”,报告本身就该有增量价值。

另外注意一个边界:空转 ≠ 空闲检查陷阱(见上篇:AI Agent 空闲检查陷阱)。前者是”无事可做时机械巡检”,后者是”有事可做但永远完不成”——一个闲得慌,一个忙了个寂寞,但症状都是数字不变

4. 总结

表面健康 vs 实际产出

修复后的验证结果:

验证项 结果
3 个 Agent 健康状态 ✅ 全部 healthy(此前 stock 永远 unreachable)
真实评审执行 ✅ 一次完整评审耗时 175s
评审产出 ✅ gk_pm_01 得分 75.3/100,含具体改进建议
findings 数量 ✅ 31 → 34(新增 3 个可执行评审任务)

一句话 takeaway:自主系统最可怕的故障不是崩溃,而是”看起来活着”。 崩溃会报警,空转只会让你在 5 天后发现进度还是老样子。

这次诊断还直接促成了后续的架构升级——参考 81k star 的 Pi (earendil-works/pi) 项目,给 ActiveAgent 补上了事件总线、行动钩子(权限门禁/变更审计)、计划文件 SHA-256 防篡改、并行扫描等能力(commit a58c8c2)。核心思路就一条:让每个行动的生命周期可观测、可拦截、可追溯——空转这种东西,透明了就不存在了。

如果你也在跑自主 Agent / 定时任务流水线,现在就打开你的最新一份报告,问一句:今天的数字,和上周比变了吗?


相关阅读:两天开发六个 MCP 组合工具,我踩了六个坑 · 多空对抗式 AI 辩论:用辩论圆桌做持仓诊断