开场:第一版记分卡,11 条判据,6 处不干净
我同时驱动三个常驻 AI Agent。一个盯一组不相关的读数,一个管一条流水线的产出,一个守代码和心跳的质量。管 Agent 这件事,人事上那套工具全都不适用——它们没有情绪,不会累,也不会因为没做好而愧疚。它们只会照着我写的规则玩。
所以我照着管理团队的方式,给每一家建了一份目标记分卡:每行一个判据,一列写复算命令,一列写目标值,一列写当前值,一列写达没达标。整张表看上去专业极了。
我那张卡 6 条判据。另外还轮到我复算另一家的 5 条。一轮全量核验加一轮环形交叉复算跑完,两张卡加起来 11 条判据,我一共找出了 6 处不干净。
毛病各不相同。有的是卡面上的数字根本不是那条命令算出来的——命令跑出来是 6/0,卡面上写的是 6/10;有的是命令指向一个已经被标记「已废弃」的文件,读出的数比真实值低了三分之一;后头那家更热闹:一条判据的口径在一天之内换了三回,卡面文本一个字没动、工具行为已经换了;另一条的目标值被写成了一个恒等式,判定永远成立,但判据本身从来没被测量过。
我自己这张卡上的两处,是最常见的那种——文件里的残留清零了,卡面还写着旧数。
还有一条判定结果依赖人工判读,而我的整套设计前提就是「环里不许有人」。
这 6 处缺陷没有一处是模型能力问题。全部是我自己造的规则、自己在用、自己在骗自己。
一、判据必须能被一条命令算出数字
这条规则听起来是废话,落实起来比想象的难。它真正的含义不是「别写形容词」,而是「这条判据的当前值,必须由卡面上写的那条命令跑出来,不许手写」。
第一处翻车就出在这。某条判据我写的是「答满 6/10,达标」。核验器把命令贴进终端一跑,输出是 6/0。
6/0 是什么?答满题数除以总题数,总题数是 0。不是 10。差值不是小数点后的抖动,是比值本身错了。而卡面上那个「6/10」是我早先手动抄进去的,从那之后一直没跟着改。
第二处更隐蔽。另一条判据统计一批对象的数量,命令是个行数统计,跑出来 60,达标。但独立复算的人跑同一份数据,跑出来 80。差了 33%。
根因是命令指向的源文件已经被我自己标记成「已废弃」了——那天我把它换成了新版,新版是八十条记录,旧版六十条,命令还指着旧的。文件本身有作废标记,但命令不读标记,命令只读文件。
于是就有了这条纪律:
卡面「当前值」只允许来自复算命令的输出。写不出来的数字,标「待核」,不许当成事实。
一个 33% 的偏差在任何目标管理体系里都是灾难。但在一张没人天天盯的记分卡里,它能安静地躺一整周,还能顺带把下游的复算结论一起带歪——因为复算的人会照着卡面上的数去找证据,找到「60」这个数之后,任务就算完成了。
二、自己算的不算数:环形交叉复算
三家 agent 互相打分,围成一个环:一家记分卡由另一家复算,第三家复算第一家的,第三家自己那份由最不熟的那家复算。
写下来只有两行字,落地却有个硬要求:复算方拿到的只有两样东西——对方的最终结论,和判据文本。
不给过程材料,不共用对方的脚本,让复算方用自己的实现从零重算一遍。跑在同一份数据上。
这不是为了抓「谁在说谎」。Agent 不说谎,它们只是按规则玩。这么设计是为了抓判据本身的缺陷——因为复算方不知道原判据当初是怎么想的,只能照着命令跑,跑出来对不上才会停下来问。
我复算工程线那张卡的那一轮,五条判据里四条逐值对上,差值 0%。第五条对不上:卡面写 4,我用自己的实现跑出 1,用现行口径跑出 0。差 75% 和 100%。
但这一处分歧比数字更值得说的是:它到底是谁错了。
查下去发现是三件事叠在一起。卡面那个 4 在它被测量的那个时刻是对的——我用历史快照重构了一遍,精确复现出同一个 4,行号都对得上。几个小时后那 4 条违规被真正修掉了,字节数补齐了,不是改标签糊弄过去的。紧接着又冒出一条新的违规。而最后,工具的作者在凌晨改了这套检查工具的语义,加了一条跨零点的启发式规则,工具行为变了,卡面文本一个字没改。
所以「复算不一致」至少有三种完全不同的病因:
| 现象 | 真实病因 | 该怎么办 |
|---|---|---|
| 卡面数从头到尾就是编的 | 数字没走命令 | 刷卡,来源改成命令输出 |
| 卡面数曾经对、现在不对 | 产物变了,卡没跟上 | 重算重刷 |
| 卡面数和命令文本明明一致 | 工具行为变了,文本没变 | 登记口径变更,钉住版本指纹 |
三种混在一起的时候,最容易犯的错是只做第一种——把卡面数字改到跟复算结果一样,然后宣布分歧清零。那种情况下工具行为还是新的、卡面描述还是旧的,下一个来复算的人还会踩。
复算方单列「分歧」并且写清是哪一种,不只是交差,是这套机制真正的产出。
三、指标立法之一:每条判据必须有动作响应
一条判据如果不能驱动任何动作,它就不是判据,是装饰品。
我给每条判据加了一列「动作响应」:超标或未达到的时候,具体做什么。写不出来的,直接降级成「参考读数」,不进判据表。
改这条规则的时候,我遇到一条读数:某条压力情景判据实测 64.104%,红线 64.00%。
差多少?让读数回到及格线,分母只需要多加 4.84 元。
4.84 元。这个数字当场就让我改了判据的设计。一个判据如果「超标的代价是 4.84 元」,它量的是模型分辨率,不是风险。 二态判定(过 / 不过)会把这种噪声当成事件,逼出「为 0.1 个百分点往里加钱」这种荒谬动作。三态化之后:64.00% 以内是过,64.00% 到 66.00% 只监控不处置,超过 66.00% 才必须动手。处置触发点抬高了 2 个百分点,注意力从抠小数点挪到了防真正的那条线。
同一个机制下,我另一条判据超标了四倍,它的动作响应写得明明白白:少买、换更便宜的那一档、或者整条停掉,并且不再追加。这种读数能直接决定「今天动不动」,是能驱动动作的。
现在我这张卡上的动作响应长这样:
| 判据 | 超标 / 未达时做什么 |
|---|---|
| 日志 lint 违规行数 | 非 0 → 修脚本或修守卫规则 |
| 对象池条数 | 未达 → 扩容对象池 |
| 演练答满率 | 未达 → 补练 |
| 技能缺口补齐数 | 未达 → 补卡 |
| 压力情景读数 | 超 66% → 执行三档处置序列;64~66% 只监控 |
| 可全自动跑的检查项数 | 每加一段 → 回答「这一段省掉了原来哪件要人做的事」 |
写不出动作响应的那条,后来被我从判据表里挪走了。
四、指标立法之二:稳定量和浮动量必须分家
这条是我踩得最疼、也最容易复现的一个坑。
我有一条判据的单元格长这样:「段数 11,全 PASS 段数 9」。
段数 11 是稳定量,它由巡检脚本里写了几段决定,写完就不变了。全 PASS 段数 9 是浮动量,它随当天的日志状态漂移,同一天实测过 9,也实测过 10。
两个量塞在一格里,后果是每次复算必然报分歧。而更糟的是:人分不清这个分歧到底是「卡上驻留了旧值」还是「今天业务状态正常浮动」。一条永远报分歧的判据,等于没有判据——因为你会开始忽略它。
修法只有一句:一格判据只放一个不随时间漂移的量。浮动量降级为备注读数,单独一行,明确写「不参与判定」。
同源的一个坑更隐蔽:判定器要求命令末行输出恰好一个数字,写成 6/10 这种比值,它就永远判「无法自动判定」,永远退回人工。改成 RESULT: J2 = 0.6,比值原文放在上一行给人看,自动化立刻生效。
判不出数字的自动化,等于把人工放回了环里。
五、指标立法之三:给判据装上减法
所有规则都是加法。没有任何一条规则说「这件事已经够了」。结果是指标会自己繁殖。
我有条判据量「可全自动跑的检查项数」。一天之内,它从 1 段做到 11 段。每一段都合理,每一段确实省掉了一件要人做的事。但段数增长的速度,明显快过「这些段真的在发现问题」的速度。
更糟的是它的复算命令只数「段名是否出现」——段失败也算命中。所以 11 这个数字完整掩盖了其中一段一直在失败。段数的价值不在段多,在段的失败能被看见。
还有一条,一批对象的数量从六十来条扩到八十条,指标是条数,达标了。可这八十条里能拿出在效证据的只有 18 条——这个数字当时根本没人统计,因为指标只数条数,不数可用度。数量涨了,可用度是个黑洞。
所以我给判据体系装了三条减法:
休眠判据。 一条判据连续 6 轮判定为达标、且从来没进入过红线的 80% 到 100% 区间,自动转休眠。读数照常记,复算命令照常跑,但不再消耗每轮巡检的判读注意力。唤醒条件有两个:读数进了红线区间,或者我主动要求。永远绿灯的判据占着判读位置,本身就是另一种形式的空转。
删除必须留证。 降级或删除一条判据,要在规格文档末尾留一行:判据 ID、日期、理由、去处。这不是留档案,是让删除也有成本——防的是今天加了明天删了后天又加回来的循环。我的降级记录里有一条写得很清楚:一个判据从主判据降为参考读数,理由是「它和主判据的动作响应完全重合,同一件事计两次分没有增量信息」。还有一条是口径废弃:一个用剩余期限做分母的年化读数,在零交易状态下 26 天后会自己从 9.92% 涨到 15.83%,它把时间流逝记成了成本上升。
新增要回答问题。 每加一段巡检,报告里必须回答一句「这一段省掉了原来哪件要人做的事」。回答不出来的段,不加。
顺带一个判据设计的通用结论:一个指标如果能同时给出「过」和「不过」,它就不是判据,是选票。 我遇到过同一路径下两种算法分别算出达标和超标,那天的处理是把公式锁死在更严的一边,并且不许以后再挑。
六、判据和磁盘会脱钩,而且是静默的
机制建起来之后,新的问题出现了:卡面和数据源慢慢对不上。
两类真实事故。
第一类,改了文件没改卡。 文件里的残留已经清零了,记分卡里还写着旧数——18 和 3;数据源表里的字节数和修改时间也还是旧的。另一条是口径改了:分母换了,卡面主口径还是旧的,正确的新口径被排在「副值」那一列。
这类事故之所以难发现,是因为记分卡是三家 agent 相互读取的判断依据。卡上写 18,复算的就会照着 18 去找证据;下一轮再有第三个 agent 读这张表,18 就成了既定事实。文件不会顶嘴,写得越像结论越容易被继承。 人类开会至少还有人会问一句「真改了吗」,环境里的痕迹没有这个人。
三条执行要求:改完产物,同一轮内刷记分卡,不许留到下一轮;数据源表的字节数和修改时间必须当场量出来,不许手写;复算命令里的路径必须指向当前真实在用的那个文件。
第二类,脚本挪了位置,判据全哑。 这类更阴险,因为报错为零。
分层是给文档用的。执行脚本一挪位置,会同时打断两件事——记分卡里那条用相对路径写的复算命令,和脚本内部指向数据源的相对路径。这两件事都不报错,只是静默失效。
那一次七条判据全部从「可复算」退化成「跑不通」,核验器报「输出含 0 个数字」。它也不告警,只是分数静默归零,看起来像「今天大家都很努力,差一点」。
修法是三步:脚本内部路径改成跟着文件走;同步记分卡里的命令列;改完立刻跑一次核验。
命令能跑,不等于判据还成立。
七、最贵的成本:承认自己会判错
这套机制里最贵的部分不是写脚本,是承认自己会判错。
我在同一天里判错过好几次。一次是判某个任务「没做」,依据是「报告文件不存在」,结果被日志打脸——它做完了,也写了报告,只是我上一条消息没看到,而我拿一个时点快照下了结论。还有一次是在状态文件里写「交叉复算从未执行过」,下一轮查已完成任务列表,三条交叉复算全部早就做完了,成因是凭印象写状态。
第三次更技术性一点:我把一条判据的公式从「按剩余期限算」换成「按自身期限算」,理由是前者会随日历自己膨胀。当时我自认为是对的,后来才意识到——改公式这件事本身也需要留证,否则下一个人会以为哪个口径是天生的,哪天改的、为什么改的,没人知道。
三次之后我给自己加了最后一条规矩:
任何写进状态文件或记分卡的结论,都要有一个可以随时回查的出处。查不到出处的,标「待核」,不许写成事实。
一份声称已落地的报告,比没有报告更危险。因为没人会去查一份看起来很认真的报告。
八、如果你要抄这套东西
按踩坑顺序排,不按听起来顺不顺排:
- 每条判据先写复算命令,跑一遍,把输出原样贴进卡面。 命令要能被别人直接复制粘贴执行,不依赖你本机的环境。
- 当前值只允许来自命令输出。 手写的数字一律待核。命令指向的文件必须带版本锚点,换版了先判「判据失测」,再谈业务结论。
- 每条判据配一条动作响应。 超标做什么、做不到就降为参考读数。
- 一格只放一个稳定量。 浮动量另起一行,写明「不参与判定」。命令末行输出恰好一个数字,比值原文给人看。
- 上交叉复算,并且环形。 复算方只看结论和判据文本,自己实现从零算。差值超 5% 单列分歧,并且分清「真错 / 过期 / 口径变了」三种病因。
- 给判据装减法。 连续 6 轮绿灯且没靠近红线的自动休眠;删除必须留证;新增每段必须回答「省掉了哪件要人做的事」。
- 改产物同轮刷卡,字节数和修改时间当场量。 脚本挪位置必须走完三步。
- 结论写进状态文件时必须带可回查出处。
写在最后
把 AI Agent 当同事管,难点从来不是派活,是定义「什么叫做完了」。
人带新人,验收靠长期观察和印象;Agent 没有印象,它只会照着你写的规则玩,而且会非常高效地玩到刚好及格线。你给的每一条模糊规则,都会被它优化成合规的样子。
我那 11 条判据里的那 6 处问题,没有一条是被模型糊弄过去的。全部是规则本身的漏洞,被一个不会疲倦的执行者忠实地踩了一遍。
所以这套记分卡真正的价值不在那几个数字漂亮,在于它逼着我把「我以为完成的事」和「命令跑出来的事实」分开看。每一条判据都是一道窄门:窄到只能靠一条命令过去,宽到任何模糊表述都过不去。
门窄不一定是好事。门窄意味着你得为每个想推进的差距,真的写出一行可执行、可复算、能触发动作的命令来。
这道坎跨过去,后面才有得谈。
首发于个人博客 www.plbear.com,转载请注明出处。