给你的 AI agent 配个飞行记录仪
先做个小测验,关于上个月:你交给 AI agent 多少任务?几次被检查拦下来?多少东西悄悄返了工?如果你和大多数团队一样——包括不久前的我们——这些答不上来,因为整个过程没有留下记录。所以我们让它把一切写下来,这篇文章说的就是这份日志实际讲了什么——包括它在哪儿骗人,和它看不见什么。
TL;DR —— agent 干活默认不留痕,于是关于它的一切论断都靠记忆,而记忆比错误更糟:它是自信的。修复很小——每个事件一行 JSON,用哈希链串起来,篡改即断链。我们的日志记了十个任务、约两百条事件,读它教会我们三件别处学不到的事:天真的指标会把真相整个反转(日志"显示"74% 的检查失败;真实的拦截次数是 2);没变成 commit 的失败对 git 完全不可见,只活在日志里;每份日志都有盲区,盲区要主动去画。词表可以抄走,我们的数字不行。
我们自己也答不上来
问一个在用 coding agent 的团队,你会听到很自信的回答:"挺好用""上周翻车了""省了太多时间"。这些都不是数据——是最近一次印象的残留。人对"AI 一个下午写出过很惊艳和很离谱的东西"记得清清楚楚;对频率和次数完全没概念,而做决策需要的偏偏是频率和次数。
我们发现这一点的方式挺丢人的。坐下来写复盘的时候,关于自己的工作流,最基本的问题都答不上来:检查到底拦过几次?活儿倒回去重来过几回?我们有观点,没有数字。而我们做的软件,整个产品就是"验证"——连我们都在凭感觉跑,那所有人都是。
最小可用的一行日志
所以我们加了飞行记录仪。不是平台,不是仪表盘:每个任务一个 JSONL 文件,由执行检查的同一批 hooks 写入——gate 是每个阶段往前走之前必须通过的检查——记三类事件,词表小到能背下来:
gate_run—— 一次检查执行了:哪个 phase、哪条命令、退出码多少、谁跑的。state_transition—— 任务的阶段动了:从哪儿到哪儿。judge_verdict—— 独立复核对工作下了裁决。
有两个设计决定比词表本身重要。第一,日志由执行检查的 hooks 写,不是 agent 写——一个会忘掉干活的 agent,同样会忘掉写日志。第二,每条事件都带着上一条的哈希,整个文件是一条链:改任何一行,链就断,校验脚本(check-events.py)会验链完整、时间戳单调、裁决次数。事后能改的日志叫日记;改不了的才叫记录。
我们的日志实际说了什么
十个任务下来,日志里攒了约两百条事件。值得你花时间的部分在这:我们的第一遍读法,几乎整个读反了。
原始计数说:86 次检查,64 次非零退出——74% 失败率。这数放上仪表盘,结论只有一个:流程坏了。真相正相反:在我们的状态机里,exit 2 就是多数阶段的通过码——一次成功的 gate 经常故意非零退出,因为"通过""没东西可查""带警告通过"是三种不同的结果,shell 的退出码约定分不出来。按语义读,日志说的是:**86 次检查,2 次真拦截,84 次干净通过。**天真的指标没有低估我们的问题——它凭空发明了问题。
那两次真拦截是我最爱的一段记录,因为它们"不在"的地方很妙。两次都发生在某个任务的实现阶段——晚上连续两次 commit 被拒,前后差一分钟左右,修完之后在第二天凌晨通过。你去 git 历史里找,什么都找不到:**被拦的 commit 永远不会变成 commit。**git 记住了发布出去的东西;它没有地方放"被拦下来的东西"。这两次失败唯一存在的地方就是日志——这恰恰是"记录"和"传闻"的区别。不用信我们,账本就在仓库里,被拦那个任务的 gate-events.jsonl 拿 grep 搜 "exit":1 就能对上。
它还说不出来什么(暂时)
一份日志要靠承认自己的边界来挣信任。我们的边界,是靠问"这个文件说不出什么"找出来的:
- **它只覆盖 31 个任务里的 10 个。**记录仪是中途装的,更早的活儿没有留下事件。任何"全项目平均"都是分母在撒谎,所以我们不算。
- **它在发布之前就关上了。**每个任务的日志都收在 judge 裁决那里——发布阶段发生在日志关闭之后,记录覆盖的是活儿,不是收尾。(我们知道。还没修。)
- **它结构上看不见"暂停"。**记录仪会跳过处于暂停状态的任务——暂停的任务一条事件都不写。十个任务里暂停事件是零,但我们没法从日志里告诉你,这到底是"从没暂停过"还是"暂停了看不见"。这个含糊是我们自己的 bug,写这篇文章才把它翻出来。
- **零回退——这是发现,不是胜利。**我们的设计里有把活儿退回去重来的机制,账本时代它一次都没触发。与此同时,31 个任务里有 17 个记着另一种重试——子代理派发因为质量原因重来——记在另一个完全分开的文件里。两套仪表,各看一半,谁也不是完整的工作流。
最后这一对是更深的教训:**仪表的盲区本身也是发现。**日志看不见什么,就是在告诉你:你对自己工作流的想象,哪儿是错的。
你的版本,一个下午
这些都不需要我们的协议。整个想法就是:找到工作流里那些"声称某件事发生了"的时刻,各写一行 JSON。三类事件够起步:
echo '{"event":"gate_run","phase":"test","exit":0,"ts":"2026-09-05T10:00:00Z"}' >> .agent-events.jsonl然后按这个顺序问日志三个问题:
- **天真读法会说什么?**用最糙的指标把失败数一遍——然后去查你的退出码和状态值,是不是你以为的那个意思。(我们的不是。)
- **日志缺了什么?**列出你的工作流可能处于的所有状态,找出哪些不产生事件。每个沉默的状态要么没事,要么是盲区——你得主动搞清楚是哪种。
- **哪些事 git 看不见?**被拦的尝试、被拒的评审、重跑的命令——没留下 commit 的活儿,往往正是你最想记住的活儿。
从一个任务做起——不是平台,一个任务——你就已经领先所有靠记忆争论 agent 的团队了。
上手
Agateon 在 github.com/randomgitsrc/agateon(MIT)——这些事件来自的协议,记录仪内置在 hooks 里。一行安装,无运行时:
curl -sSL https://raw.githubusercontent.com/randomgitsrc/agateon/main/install.sh | bash这篇里的数字很小、很早期——十个任务是个开头,不是结论。上一篇承诺过,数字攒够了会诚实地说;这就是那篇,连缺口一起交。但方向已经站得住:没有记录的 agent 工作流,谈不上验证没验证——它根本不可观测,而不可观测的意思是,所有关于它的争论最后都打成平手。给它配个飞行记录仪。日志会跟你的记忆吵架,而且日志会赢。