我有三台 AI agent 在干活:一台管理财,一台管求职和内容,一台管运维巡检。它们各自有工作区、各自有日志、各自在汇报末尾写「已完成」。
问题是「已完成」这三个字不值钱。11 月 7 日那天我想回答一个很朴素的问题:过去一周,这三台东西到底交付了什么、有没有假装交付?我翻了两小时日志,最后发现我答不上来——不是因为没数据,是因为数据全是形容词。
于是有了后面七天。目标很土:让三台 agent 共用一套记分卡,每条判据都能被一条命令算出数字,谁都能复算。七天做完,我踩了五个坑,每个坑都真实咬过我一口,也都留下了实测数字。
七天时间线:每天只解决一件事
先给结论的时间轴,后面每个坑都能对到某一天。
| 天 | 日期 | 当天只做一件事 | 当天新增的教训 |
|---|---|---|---|
| D1 | 11-07 | 立项:写清「交付」到底指什么 | prompt 里的形容词无法审计 |
| D2 | 11-08 | 看板挂了一次,第一次尝到「没人知道它挂了」 | 环境本身也要有判据 |
| D3 | 11-10 | 立唯一原则:判据必须一条命令算数字 | 第一版 11 条判据,6 处不干净 |
| D4 | 11-11 | 上环形交叉复算:自己算的不算数 | 差异分「硬分歧」和「口径差」 |
| D5 | 11-12 | 抓幽灵产物 | 只扫一个目录会把真产物判成幽灵 |
| D6 | 11-13 | 抓编数 | 卡面改了、脚本没改,机器照样判 PASS |
| D7 | 11-14 | 给 SKIP 正名 + 给指标装减法 | SKIP 不是失败,也不是达标 |
D3 那条原则只有一句话,但它把后面所有事都改写了:
|
|
记分卡固定六列,缺一列不合格:ID / 判据 / 复算命令 / 目标值 / 当前值 / 达标。达标列只允许 Y / N / BROKEN,后来加了一等态 SKIP。就这六列,七天里被我自己违反了五次。
坑一:判据写成形容词,等于没判据
D3 第一版我写了 11 条判据,自认为挺严谨。拿去让人逐条读,6 处读不出「怎么算」:
- 「心跳日志质量良好」——几份算良好?
- 「面试准备稳步推进」——推进到哪里算稳?
- 「保费控制合理」——合理是谁的合理?
这不是措辞问题,是结构问题。形容词判据的判定发生在人脑子里,而人脑子里没有版本号:今天说良好,明天还说良好,中间什么都没发生。更糟的是它无法被别人复算——你只能信我。
改法很机械:每条判据配一条能贴进终端的命令,末尾输出一行机器可读的结论。
|
|
|
|
有了这行,判据就从「我说好」变成「你自己跑」。D3 结束时 11 条判据全部改成命令,当天的复算结果是 PASS=11 FAIL=3 BROKEN=0 MANUAL=4。MANUAL 那 4 条是我留的尾巴,它们其实是「命令写不出来」的诚实标记,不是例外。
顺带一个副作用:一旦要求「一条命令算出来」,很多判据会自己露馅。有一条我原本写「风险度可接受」,要写成命令时才发现分母有两个口径,一个算出 23.06%,一个算出 31.50%。两个数都「可接受」,但这条判据实际上在我选口径的那一刻才成立——那就不叫判据,叫选票。
第二个副作用更要命:目标值如果拍在动作改不动的地方,判据就永远驱动不了动作。 我有个阈值卡在 64.00%,实测 64.104%,判 N。要让这个数回到 64.00%,可用资金只需要再增加 4.84 元——在保证金的日波动里连水花都算不上。这条判据量的是噪声,不是风险:它今天红、明天自己绿,唯一稳定产出是让我每天纠结 0.1 个百分点。
同一张表上还有反方向的一种:判据本身在数学上不可翻转。年化保费率红线 2%,实测 9.92%,我顺手反解了一下,要把年化压回 2%,建仓日得在 392 天前——而那个合约 392 天前还没挂牌。也就是说,从今天起一笔交易都不做,这个 N 也会永远是 N。它没错,它是纪律红线;但它不能和「可优化指标」混在同一层,否则表上永远亮着一条驱动不了任何动作的红灯。
所以坑一真正的修法有三步,不是一步:判据写成命令 → 目标值拍在可改变区间 → 纪律红线和可优化指标分家。只做第一步,你会得到一张能算出数字、但数字没用的卡。
坑二:自己给自己打分,编数看不出来
第一版记分卡是各家自己填的。填了三天,看起来很绿。D6 我做了件很笨的事:不看卡面,直接跑卡上那行命令。
抓到两个:
第一个是「改卡面不改脚本」。 卡面写 3.45%,卡上那条复算命令的输出是 2.80%。它没撒谎,它只是改了结论没改证据。机器一比对就露:卡面值在命令输出里找不到,判「编数嫌疑」。那天这项指标从 0 升到 1。
第二个是「自述 PASS 数 ≠ 实测 PASS 数」。 巡检脚本自述 11 段全 PASS,我独立跑一遍是 10 PASS / 1 FAIL。差的正是那个 FAIL——一段只看「段名出现过没有」的检查,段 FAIL 也算命中,于是它自己把自己算成了 PASS。
改法是环形交叉复算,三家互检:
|
|
配套两条硬规则:
- 复算结果与自评相差超过 5%,必须单列「分歧」,写明谁对、为什么。
- 硬分歧为 0 才算这一轮干净;口径差不算分歧,但也必须登记,不许含糊过去。
为什么是环形而不是找第四个审计者?因为这仨互相没有利益冲突,也没有共享的 KPI。它们的共同利益是「别被我发现造假」,而互相检查恰好让这个共同利益失效。这比外部审计便宜太多——成本只是每两天多跑一次命令。
D7 那轮的读数是:PASS=17 / FAIL=1 / BROKEN=0 / MANUAL=1 / SKIP=0,硬分歧 0、编数嫌疑 0。唯一那条 FAIL 是年化保费率 5.57% 对红线 2.00%,一个真实缺口,不是判据坏了。
这里补一条同源的坑,比编数更隐蔽:备份做在改后,等于没有备份。 有一次改卡面,改完把当前文件拷成 .bak 当「改前备份」。核查时我发现备份和当前文件的 SHA256 完全相同——它备份的是改后的结果。真正的改前原文是另一份,72,564 B,而当时卡面已经 74,308 B。判备份是否有效,不能只看文件在不在、mtime 新不新,要看 SHA(备份) ≠ SHA(当前)。
交叉复算真正有价值的地方,是它能把「看起来是分歧」的东西逐条消解掉。有一轮 8 条分歧,其中 3 条当场对平:一条浮盈口径差 60(−115 对 −175),用快照的指定行逐字对上了,属于两家取数时点不同;一条保证金反解不出来,最后封存成区间;还有一条更典型——一边用 38.31% 的隐含波动率,另一边从本地行情原始 JSON 反解出 47.08%,逐字比对后确认 38.31% 是借用值,当场被取代。剩下 5 条不是数字问题,是「这一项该去哪查、查哪个字段、回填到哪一行」,于是写成 8 行核对单,每行标券商页面、字段、记什么、填进哪个文件的哪一行。
这就是「硬分歧」和「口径差」的区别:硬分歧是两个数都自称实测、谁也不让;口径差是同一件事的两个取数时点。前者必须判谁对,后者只登记,不许含糊。把两者混在一起数,你会得到一个永远大于 0 的分歧数,然后习惯它、忽略它。
坑三:幽灵产物——文件在,人以为不在;文件不在,人以为在
「幽灵产物」是我造的词,指 agent 声称写了、磁盘上却没有的交付物。D5 我以为抓到了一堆,结果三个方向全踩了。
踩法一:只扫一个目录。 那天的内容线产出 8 份、95,744 B,全部落在另一台 agent 的素材目录里,我自己的工作区一份没有。按单目录扫,结论是「零交付,全部幽灵」——差点把一份做完的活打回重做。改法写死成规则:核验内容线产物必须同时扫三根目录,任一命中即算落盘。
踩法二:按汇报里的文件名 Test-Path。 一份报告汇报写 ROLL_WINDOW_PLAYBACK_CALC.md,磁盘上叫 ROLL_WINDOW_PLAYBOOK_CALC.md。差一个词,字节 4,964 完全对。如果按「名字搜不到即判幽灵」,一次改名就能误杀一份已完成的活。改法是先枚举目录,再判,不许拿汇报的名字直接查存在性。
踩法三:把解析错误当成文件不存在。 C2 这条判据说有 1 个幽灵产物,我让另一台去定位,结论是文件真实存在,12,287 B,是解析器把声明里的双反斜杠路径当成 UNC 路径解析失败。这不是幽灵,是尺子坏了。修法只动路径归一化,判据目标值、阈值、扫描范围一律不动——修尺子不等于放宽标准,这两件事必须分开,否则下一次真幽灵就会被「修尺子」的名义放过去。
三个踩法合起来是一条:判幽灵的难点不在「找文件」,在「确认你找的地方和它写的地方是同一个地方」。 所以现在的判定顺序是:先枚举所有可能根目录 → 用文件类型决定比什么(一次性交付物比字节数;*-index.md 这种累积追加大文件只比存在性 + mtime,直接比字节会产生假阳性)→ 全都不命中才判幽灵。
坑四:SKIP 被当成 BROKEN,一条命令的退出码毁掉整张卡
D7 最值得写的一个坑,因为它纯粹是工程细节,但杀伤力最大。
有条判据是「当天交付物声明与实际文件的对账数」。它内嵌了一个扫码脚本。规则很清楚:当天心跳日志还没生成时(00:00 到第一次心跳之间),没有数据可判,扫码脚本以 exit 3 (SKIP) 退出。
问题是外层包装脚本在末尾加了一句退出码守卫:
|
|
内层 exit 3 被原样透传,验证器看到 returncode != 0,判 BROKEN「命令跑不通」。而输出里明明写着 missing=0。于是每天凌晨那半小时,一张本来全绿的卡上必然亮一条「命令坏了」——不是数据坏了,是语义没翻译。
改法不是改扫码脚本(它的 exit 3 约定还被别处依赖),而是在卡面那一行命令里加一层翻译:
|
|
三个要点,每个都对应一次误判:
- SKIP 豁免只给一种情形:
status=SKIP且输出含log file not found。别的 SKIP(比如索引缺失)一律降级为 FAIL,不享受豁免。 - 末尾显式归零
$global:LASTEXITCODE = 0。内层退出码不再透传,这是 BROKEN 假故障的直接机关。 - 不许用 0 冒充 SKIP。SKIP 期间达标列记
SKIP,既不写 Y,也不把 SKIP 窗口里的missing=0记成「达成」。
实测对照,改前改后各一次:
|
|
这条坑的普适版本是:监控系统的假故障,比真故障更消耗信任。 一条每天必红的假红灯,三天之后就没人看了;等真红灯亮起来,它也混在噪声里。
坑五:指标自我繁殖,C5 从 1 涨到 12
C5 这条判据量的是「可全自动跑的检查项数」。它是所有判据里最危险的一条,因为它奖励加法。
数字很难看:2026-11-10 一天,从 1 段做到 11 段。 到 11-14 已经是 12 段。每加一段我都觉得自己在进步,实际上我在给自己造工作量,也在给「看起来很努力」造证据。
更糟的是它的复算命令只数「段名是否出现」。段 FAIL 也算命中。所以 C5=11 的那天,其中一段的 BOM 检查是 FAIL 的——C5 用 11 这个漂亮的数字,完整掩盖了那次失败。一段只会报 PASS 的检查,加多少段都是装饰。
D7 我给它装了一道闸门,规则只有一句:
|
|
然后拿这句话去盘 12 段。盘法是逐段填三列:原来谁做、多久做一次;现在谁做;如果这段失效,谁来兜。
结果两条直接答不出来:
| 段 | 答不出来的地方 | 今天的增量省活 |
|---|---|---|
| 5-BYTES | 声明 vs 磁盘字节对账,已被 11-CLAIM 完整接管(5 源 vs 2 源) | 0 秒/次 |
| 7-E2 | 心跳乱码回扫,已被 8-ENCGUARD 的 G1/G3/G4 严格包含 | 0 秒/次 |
两条都是「历史上有功,今天零增量」。能背出它 11 月 10 日的历史功勋不算答案——闸门问的是现在。 于是 12 段 → 10 段,这是 C5 从 1 涨到 12 之后第一次反向收敛。
盘的过程中我还顺手扫了一个反向问题:有没有哪一段「只会报 PASS」?逐段读源码的 FAIL 分支,12 段全部有显式 FAIL 判定,0 段符合。这条结论对我不利——我本来以为能再砍两段——但如实写进去了,没有为凑清单编造。
另外两处弱化事实也一并登记:有一段的核心判定只报 WARN 不报 FAIL(漂移看不见失败等级);还有一段的 FAIL 无人能读到(它检查的东西和后端同生共死,后端真挂了巡检自己也跑不动)。这两条不改,只记在观察项里——发现问题不需要当天就动刀,但必须当天落纸。
七天之后,这套东西长什么样
三份记分卡,每份 70~90 KB,头部写实测时刻和 Get-Item 字节数。每轮巡检的读数长这样:
|
|
唯一那条 FAIL 是真实缺口,年化保费率 5.57% 对红线 2.00%。它红了十几天,我没有把它改成绿——它是纪律红线,不是可优化指标,动作是在预算内重排结构,不是改数字。
三份卡的新鲜度也进了检查:每份必须 24 小时内更新,超时就要重算。看板现在 569 条任务:done 560 / in_progress 3 / todo 3 / backlog 3。三家各 1 条在跑、1 条待办、1 条后备——这个整齐度是七天里第一次出现,之前长期是「有的满载、有的空转」。
复算脚本自己也别太信
判据全靠一条命令,那条命令本身就成了新的单点。七天里它坏了三次,每次都是同一个模式:输出看着对,机器读出来是另一个意思。
第一次:结论行里放了两个数字。 有条约定的结论行是 RESULT: <ID> = <数>,判定器只认「恰好 1 个数字」。我写了 RESULT: J2 = 6/10,两个数字,判定器每次都判「无法自动判定」,退回人工。改了三次才发现问题不在判定器笨,是我把两个信息塞进了一个只允许一个数字的字段。
第二次:比值没算成小数。 另一个任务写 673/813,判定器把它当两个数,直接失效。后来加了条硬规矩:RESULT 行里的任何比值都必须先算成小数(0.828),否则这条判据永远 MANUAL。
第三次最隐蔽,是个过渡态。 SKIP 修好之后,验证器还不认 RESULT: C2 = SKIP——它的正则只匹配数字,匹配失败就回退去扫全行数字,结果把 ID 里那个 2 当成读数,判 FAIL(2 对目标 0)。也就是说,我修好了 SKIP 语义,却在下一层被「扫数字」的兜底逻辑打回去。过渡期只能靠人工知道「这个 FAIL 不算故障」,等验证器补上 SKIP 一等态才消失。
三次的共性:尺子上的刻度也要有判据。 我的做法是给核验器本身加自测——跑一遍全量,看 PASS/FAIL/BROKEN/MANUAL/SKIP 五类是不是都能被正确分类,速度也算一个观测指标(全量一轮 9.3 秒,之前是 3 分钟,判据自动化已经不是瓶颈,瓶颈回到「读五份 result 全文 + 判断」)。
五条能直接抄的规则
如果只抄五条,抄这五条:
|
|
每一轮巡检固定问三个问题:这轮有几条判据翻了?翻的原因是什么?有没有哪条判据连续 N 轮绿灯但从来没驱动过任何动作?第三个问题问出来最扎人——永远绿的判据占着判读位置,是另一种空转。
我没解决的三件事
不吹圆满,三件还挂着:
- 判据读死快照。 有一条判据的复算命令指向一份首行已标「已废弃」的旧池文件,机器照样判 PASS。数字对,指向错。机器核验抓不到这种错,只能靠人读。
- SKIP 的端到端没有独立复跑。 我用无日志窗口复现了 SKIP 形态,但没有真的造一个「运行中日志突然缺失」的端到端场景。现在只有单点复现,没有链路证明。
- 减法的阻力在心理上。 删两段花了我一晚上说服自己:这两段是我亲手写的,删掉像是承认前几天的活白干。但账算得很清楚——每天多跑两段零增量的检查,一年就是 700 多次白跑。
结尾:记分卡不是给人看的,是给下一个我用的
七天里最贵的一课不是「怎么写判据」,是承认自己会判错,并且把这件事写进流程。
三台 agent 里有一台在汇报阶段自己抓到自己两处错并当场改:把结论行的两个数写反了,把副本字节当成生产脚本字节。这不是我教它的,是流程逼出来的——因为每条结论后面都跟着一条可复算的命令,写反了下一轮就会被自己的命令打脸。
所以记分卡的真正用途不是给我看今天绿不绿。是给三天后的我看:当时你凭什么说它好了?证据在哪一行?还能不能重跑一遍?
如果这三个问题答不上来,那这张卡就是装饰品。我花了七天,才把装饰品换成一堆能跑的命令,又花了一天删掉其中两段。
标签:AI Agent · 工程治理 · 指标设计 · 多 Agent · 自动化
系列:三台 agent 的记分卡实践 · 第 7 篇