本文最后更新于 2026年7月12日 晚上
一个连续 4 天的”未闭环”Bug
一套量化交易系统配备了一个 AI 日记系统,每天凌晨自动反省:做了什么、哪些计划没落地、怎么改进。这个自省机制基于 PDCA 循环——计划(Plan)、执行(Do)、检查(Check)、处置(Act),每天对账。
某天,日记系统记录了一个偏差:宏观分析模块依赖的 GPR Index(地缘政治风险指数)外部数据源下载超时,看板上的地缘风险字段显示为问号。改进措施是:为数据源添加 HTTP 重试 + 本地缓存兜底。
然后,这个偏差开始”复现”:
日期
日记记录
闭环状态
第 1 天
发现 GPR 超时,计划下周加缓存兜底
待办
第 2 天
“GPR 修复未执行”,闭环率 25%
❌ 未闭环
第 3 天
“GPR 修复挂账 3 天 → 升级为系统性缺陷”
❌ 未闭环
第 4 天
仍在持续标记,改进措施闭环率 0%
❌ 未闭环
日记系统的连贯性检查(Coherence Check)也发出了警报:连续出现天数已达升级阈值,趋势恶化。一切看起来都很严肃——这是一个需要立即处置的系统性缺陷。
但事实是:修复早在第 2 天就已经完成了。
证据:修复一直都在
直接检查代码库,geopolitical_risk_tracker.py 中赫然存在完整的两层防护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
GPR_CACHE_PATH = os.path.join(DATA_DIR, ".gpr_cache.xls") GPR_MAX_RETRIES = 3 GPR_RETRY_DELAY = 5
def download_gpr_data(): """下载 GPR Index Excel 文件(含重试 + 缓存兜底)""" last_error = None for attempt in range(1, GPR_MAX_RETRIES + 1): try: resp = urllib.request.urlopen(req, timeout=30) data = resp.read() with open(GPR_CACHE_PATH, "wb") as f: f.write(data) return df except Exception as e: last_error = e if attempt < GPR_MAX_RETRIES: time.sleep(GPR_RETRY_DELAY)
cached_df = _load_gpr_from_cache() if cached_df is not None: return cached_df raise RuntimeError(f"GPR Index 下载失败且无可用缓存: {last_error}")
|
再看运行产物:
1 2 3 4 5 6 7
| 1 2
.gpr_cache.xls 2.7 MB (缓存文件存在,说明成功下载过) geopolitical_risk.json 更新时间:第4天 03:06
|
打开 JSON 文件,数据完好无损:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 1 2 3 4 5 6 7 8 9
{ "assessment_date": "2026-07-07", "global_gpr": { "current_value": 173.6, "percentile": 95.4, "level": "极高", "trend_3m": "上行" } }
|
GPR Index 不仅下载成功、缓存成功、还正常生成了风险评分。修复早已落地,代码在跑,数据在更新,看板上不再有问号。 但日记系统连续 4 天报告”未闭环”。
这就是幽灵偏差(Ghost Deviation):一个已经解决的偏差,被自省系统反复标记为”未解决”。
根因分析:自省机制是文字搬运工,不是验证器
为什么会产生幽灵偏差?拆解这个 AI 日记系统的反省流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 1 2 3 4 5 6 7
第 N 天日记的改进措施 ↓ 写入待办队列(fact_store) ↓ 第 N+1 天反省:对照"昨日安排表"逐项打勾 ↓ "这个任务执行了吗?" → 看待办状态 → 未标记完成 → ❌ 未闭环
|
问题出在最后一步。反省系统判断”任务是否完成”的方式是查看待办状态——这个待办是被人/Agent 标记为完成了,还是仍然挂着?
这本质上是文字对文字的验证:昨天的日记写了”GPR 修复”,今天的反省检查”修复的待办是否被标记完成”。如果执行环节走了另一条路径(比如直接改了代码,没有回来更新待办状态),反省系统就永远看不到”已完成”的信号。
1
2
文字(日记计划)→ 文字(待办状态)→ 文字(反省结论)
↑ 缺失的环节:实证验证 ↑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 用 PDCA 的语言来说,**检查(Check)环节缺失了实证维度**。它检查的是”计划是否被标记为执行”,而不是”执行的结果是否存在于现实中”。
这是一个微妙但致命的陷阱:反省系统越是”认真”地复述昨日计划、越是连贯地追踪偏差趋势,幽灵偏差就越逼真。连续 4 天的趋势图、升级阈值的警报、闭环率的下降曲线——这些指标看起来都很有说服力,但它们追踪的是一个**不存在的偏差**。
## 幽灵偏差的三个产生条件
幽灵偏差不是随机 bug,它需要特定条件才能持续存在:
### 条件一:执行路径绕过了状态更新
修复代码时走了”快路径”——直接改代码、验证、提交,没有回到待办队列更新状态。这在工程实践中极其常见:你不会在修完 bug 后专门去某个待办系统点”完成”,尤其是当修复本身就是顺手做的事。
### 条件二:反省系统只读状态,不验证现实
反省机制设计为”对照计划表打勾”,而非”去代码库/文件系统/运行日志里验证结果”。这是一种**只信账面、不点现金**的审计方式。
### 条件三:偏差被赋予”惯性”
一旦某项被标记为”偏差”,连贯性检查会自动追踪它的”连续出现天数”并计算趋势。但这个追踪是**单调递增**的——只要状态不变,天数只会增加,不会自动归零。一个已经修复的问题,如果没有人在状态层面”销账”,它的偏差天数会一直涨下去,最终触发警报升级。
```makefile
1 2 3 4
第1天: 偏差! (天数=1) 第2天: 仍在追踪... (天数=2) ← 实际已修复,但无人销账 第3天: 升级为系统性缺陷!(天数=3) 第4天: 趋势恶化!(天数=4) ← 完全脱离现实
|
修复方案:Check 环节的实证化
幽灵偏差的根因是 Check 环节缺少实证验证。修复思路:不要问”待办标记了吗”,要问”结果文件在不在”。
对账方式从”状态查询”改为”产物验证”
以 GPR 修复为例。正确的检查方式不是查待办状态,而是直接验证产物:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
import os, json
def verify_gpr_fix(): """实证检查:GPR 修复是否真的落地""" checks = []
code_path = "scripts/cycles/geopolitical_risk_tracker.py" code = open(code_path).read() checks.append(("代码含重试逻辑", "GPR_MAX_RETRIES" in code)) checks.append(("代码含缓存逻辑", "_load_gpr_from_cache" in code))
cache_exists = os.path.exists( os.path.expanduser("~/.autoquant/cycles/.gpr_cache.xls") ) checks.append(("缓存文件已生成", cache_exists))
json_path = os.path.expanduser("~/.autoquant/cycles/geopolitical_risk.json") if os.path.exists(json_path): data = json.load(open(json_path)) gpr = data.get("global_gpr", {}) checks.append(("GPR数据有效", gpr.get("current_value") is not None)) else: checks.append(("GPR数据有效", False))
return checks
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11
| 1 2 3 4
代码含重试逻辑 ✅ 代码含缓存逻辑 ✅ 缓存文件已生成 ✅ GPR数据有效 ✅(current_value=173.6)
|
四项全绿。幽灵偏差当场消失。
偏差销账机制:自动归零
连贯性检查追踪偏差天数时,必须有一个自动归零的出口:每次反省前,先用实证检查(而非状态查询)验证偏差是否仍然存在。如果产物验证通过,偏差天数立即归零,不再参与趋势计算。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1 2 3 4 5
反省开始 ↓ 实证检查(验证文件/代码/数据) ├─ 通过 → 偏差天数归零,状态改为"已闭环" └─ 失败 → 偏差天数 +1,继续追踪
|
执行路径补全:顺手销账
根本的修复是让执行路径不再绕过状态更新。但与其要求”修完代码后手动去点完成”——这违反人性——不如让执行环节本身产生可被检查的信号。上面的实证检查就是一种”被动信号”:只要代码改了、文件生成了,检查自然通过,不需要任何人主动销账。
更深一层的教训:自省系统也会自欺
幽灵偏差最讽刺的地方在于:它恰恰发生在专门用于发现偏差的系统里。
这套日记系统的设计目标就是”不放过任何偏差”——它有连贯性检查、有趋势追踪、有升级阈值。但所有这些精密的追踪机制,都建立在同一个脆弱的假设上:检查的对象(待办状态)等于现实(修复结果)。
当这个假设不成立时,自省系统从”发现问题的工具”变成了”制造问题的工具”。它生成的不是洞察,而是噪音——连续 4 天的”系统性缺陷警报”,占用了注意力,消耗了改进带宽,而真正需要关注的问题反而被淹没。
1
2
设计意图: 计划 → 检查状态 → 发现偏差 → 改进
实际行为: 计划 → 检查状态 → 发现幽灵 → 追踪幽灵 → 升级幽灵
这不是某个系统的个例。任何”自省”或”监控”机制都面临同一个根本问题:**你监控的指标,和你以为它在监控的现实,可能根本不是一回事。**
- 监控系统告警了”服务不可用”,但服务其实在正常运行——因为健康检查打的是一个错误的端点
- CI 报告”测试全绿”,但测试根本没有覆盖核心路径——因为覆盖率指标只看了行数不看分支
- 日记系统报告”偏差持续恶化”,但偏差早已修复——因为反省只对账文字不对账现实
所有这些幽灵的共同根源是一个:**把”指标的表示”(文字、状态、标记)误当成了”指标的所指”(现实、产物、效果)。**
## 小结
幽灵偏差的教训可以浓缩成一句话:
>
**检查(Check)不能只查账面,必须点现金。**
一个负责任的自省系统——不管是 AI 日记、CI/CD 监控、还是人的每日复盘——必须包含实证验证环节。否则,它追踪的偏差、计算的趋势、发出的警报,可能全部指向一个早已不存在的问题。
PDCA 循环里最容易掉链子的不是 Plan,不是 Act,而是 Check。因为 Check 是唯一需要**跳出文档、面对现实**的环节。它不能只读自己写下的东西,它必须去验证那些没有写在计划里的、但真实存在于文件系统、代码库、运行日志里的结果。
否则,你的日报系统就会变成一个幽灵发生器——一遍又一遍地复述一个已经死去的偏差,同时对自己说:看,这个偏差还没解决,我得多记几遍。
---
*幽灵偏差(Ghost Deviation):已消除的偏差在自省系统中持续产生”未闭环”假象的现象。根因是 Check 环节以状态查询代替实证验证。解法:产物驱动的检查 + 偏差自动归零。*