我的 AI 自进化系统每天都在空转,而 cron 报告一切正常
一次对 AI 自进化管线空转问题的完整诊断——616 个积压 session、被反复扫描的 watchdog、以及一个让知识库停止增长三天的隐藏 bug。
本文索引12
我的 AI Agent 自进化系统每天早上 3:45 都会跑一次"上下文进化"任务。它一直报告"运行成功"。直到我发现它的知识库三天没涨了。
一切正常,直到我看了数字
我在服务器上跑了一套 AI Agent 自进化管线。其中有个叫 ACE Context Evolution 的 cron job,每天凌晨 3:45 启动:扫描 agent 会话、提炼洞察、合并进一个知识库文件,让我的 agent 越用越懂我。
那天早上它推送了一份报告:
[03:45] Running Reflector (session analysis)...
[]
[03:48] Running Grow-and-Refine...
[SKIP] 上下文 124 chars < 阈值 2000
✅ ACE Evolution Complete
看起来完美。退出码 0,三分钟跑完,"✅ Complete"。
但我是个会看数字的人。我翻了历史产物文件,发现不对劲:
| 日期 | 活跃条目数 |
|---|---|
| 8/12 | 17 |
| 8/13 | 19 |
| 8/14 | 4 |
知识库从 19 条缩到 4 条。而且这 4 条还是老面孔——requests 超时、Python 3.12 语法、eval 安全风险,都是几天前的旧洞察。三天没有任何新东西进来。
系统在假装工作。
那个 [] 是什么
先解释一下这套管线在干嘛。ACE(Agent Context Evolution)的流程是:
Reflector (读 agent 会话轨迹)
→ 提取结构化洞察 (pattern/bug/knowledge/preference/warning)
→ Delta-Merger (去重、合并、淘汰)
→ Bundle (生成知识库文件)
关键在第一步。Reflector 去数据库里找"未处理的会话",每个会话用 LLM 读一遍,提炼洞察。
cron 报告里的 [] 不是报错——是 Reflector 处理了一批 session,但产出 0 条洞察。空数组被传给下游,下游如实打印了个空,然后一切都"成功"了。
我手动跑了一遍,得到更精确的信息:
{"processed": 3, "insights": 0, "failures": 0}
处理了 3 个会话,0 条洞察。为什么?我查了数据库,发现一个更吓人的数字:616 个会话积压未处理。
一个每天跑的 cron,为什么会积压 616 个?
根因链:三个小问题叠在一起
根因 1:Reflector 不挑食
_get_unprocessed_sessions() 把所有会话都列进处理队列,不管内容多少。我的系统里有 595 个 cron 会话——大多是 watchdog 类的短会话:
cron_1d46b7ac95c5_20260814_032123 msgs=6 dur=8s
cron_1d46b7ac95c5_20260814_022022 msgs=7 dur=10s
cron_1d46b7ac95c5_20260814_011922 msgs=4 dur=6s
6 秒、4 条消息的 watchdog 会话,能提炼出什么洞察?没有。它就是设计来"看一眼然后退出"的。
而真实交互会话平均 12.5 条消息、716 秒——那才有东西可挖。
根因 2:取最新,而不是取最早
--limit 20 每次处理 20 个。但排序是 ORDER BY started_at DESC——最新的 20 个。
最新的会话是什么?就是刚跑完的 watchdog。每小时一个,永远占满队首。
结果:cron 每次都在处理同一批 watchdog,238 个真实交互会话永远排在后面。就像餐厅排队,队头永远是一群只看菜单不点菜的人,真正要吃饭的顾客在队尾站了三天。
根因 3:零产出 = 永不标记
这是最隐蔽的一个。Reflector 处理完一个会话后,把洞察写进日志。但如果产出 0 条洞察,什么都不写——"已处理"的标记也不存在。
所以 watchdog 会话处理完,等于没处理。下次 cron 启动,它又出现在队列里。每天被 LLM 读一遍,每天产出 0,每天重新排队。
616 个积压里,有多少是被反复扫描的? 我看了一眼:mc-checker-watchdog 一个 job 就贡献了 63 个。它每小时跑一次,每次都被 Reflector 当"新会话"处理一次。
三天白跑,不是偶然,是必然。
修复:三个改动
A. 过滤短会话
给查询加两个阈值——少于 3 条消息、持续不到 60 秒的会话直接跳过:
MIN_MESSAGES = 3 # 至少 3 条消息
MIN_DURATION_SEC = 60.0 # 至少持续 60 秒
rows = conn.execute(
"SELECT id, MAX(started_at) AS latest FROM sessions "
"WHERE message_count >= ? "
"AND (COALESCE(ended_at, started_at) - started_at) >= ? "
"GROUP BY id ORDER BY latest ASC",
(MIN_MESSAGES, MIN_DURATION_SEC),
)效果立竿见影:616 → 145。watchdog 会话大部分直接消失。
B. 从最早的积压开始处理
排序从 DESC 改成 ASC——先消化 backlog,再处理新会话:
# hermes 列表按 started_at ASC 生成,取头部即最早 backlog
sessions = sessions[:limit]这样每天 20 个,一周左右清完积压,之后维持当天新增。
C. 零产出也标记(真正的根因)
新增一个 processed-sessions.jsonl,无论产没产出洞察,处理过的会话都写进去:
def _mark_processed(session_id: str):
"""记录 session 已处理(幂等),即使 0 产出也标记"""
# 先查重,避免重复行膨胀
# 然后 append: {"session_id": ..., "processed_at": ...}查重逻辑保证同一会话只记录一次。现在 watchdog 被处理一次,就永远离开队列。
为什么我说这是"假装工作"
修复本身不难。真正让我警觉的是这个系统的反馈信号。
cron 报告显示 "✅ Complete",但它在空转。知识库三天没涨,但没有任何告警。自进化系统最大的风险不是崩溃——崩溃会出声。是静默退化:一切正常地、持续地产出"无"。
这让我想到一个更普遍的规律:自动化系统需要"负结果可见"。
LLM 批量处理脚本必须有 --limit,否则积压场景全量扫描必超时——这是 8 月 5 日就踩过的坑,当时只修了超时,没修根因。
"0 条洞察"和"报错"一样是有效信息。空数组应该被记录、被统计、被上报——而不是静默通过管道。
如果你也有定时跑 LLM 批处理的管线,建议检查三件事:
- 你的处理队列是"处理过"还是"产出过"? 零产出的任务有没有被标记为已完成?
- 排序方向对吗? 取最新还是取最早,在积压场景下是两个完全不同的系统。
- 空结果可观测吗? 下游收到空数组时,是打印一个优雅的
[],还是记录一条"空产出"?
数字收尾
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 未处理积压 | 616 | 143(持续下降) |
| watchdog 重复扫描 | 每天重复 | 一次即止 |
| 知识库新增 | 0/3天 | 恢复增长 |
修复代码在 ~/.hermes/scripts/evolution/reflector.py。如果你的 agent 也跑自进化管线,值得看一眼它的队列实现。
系统静默退化的可怕之处在于:一切正常,直到你翻数字。
这是我维护的 AI Agent 自进化系统的一次真实排障。想了解整个系统架构?看我之前写的 self-evolving-system-architecture 和 agent-evolution-bio-inspired。