我给自己写了个不会说谎的 coding agent
前天晚上,我把一个折腾了一个多月的个人项目推上了 GitHub。
仓库叫 keel-agent,中文名龙骨。推完的那一刻,star 数是 0,fork 数是 0,watcher 还是我自己。
按现在开源项目的排面标准,这个首发寒酸得有点好笑。毕竟隔壁腾讯开源个 AI 助手,三个月就是六千五百颗星,上一篇我刚写过它。而我这个,发布当天数据是整整齐齐一排零。
但我是真的兴奋。因为开源前那两天我干的事,是补测试、写验证记录,把十几个只有真机才能暴露的问题一个一个摁死,这种笨功夫做完,心里是有底的。
这个项目藏着我今年最想回答的一个问题。coding agent 都这么多了,Claude Code、Codex、OpenCode,我为什么还要自己再写一个?
答案特简单,因为它们都不会说「不」。
你肯定也遇到过这个场景。你让 agent 修个 bug,它噼里啪啦一顿操作,改文件、跑命令,然后特认真地跟你说,已经修复完成了。你一跑测试,红的。
模型说做完了,和事情真的做完了,中间隔着一整个太平洋。
这个缺口在交互式场景里还能忍,你盯着呢,发现红了骂一句它就回去重改。但在 CI 里,在自动化代码维护里,这就是致命的。流水线需要一个可依赖的信号,不是一句口才很好的放心吧。
keel 就是冲着这个来的。它是一个 coding agent harness,浓缩下来就三个约定。
第一个约定,会话即事件流。
keel 的会话是一条只能追加的事件日志,一个 jsonl 文件。LLM 的每次请求响应、每次策略判定、每次工具调用、每份验证证据,全是带序号的事件落进去。对话历史、花费、会话状态,全是对这条日志做纯推导得到的视图。上下文压缩也只是换了个视图,日志一个字不动。
这条日志直接长出了三个命令。replay,只读回放完整执行轨迹,每一步的判定和成本都在。fork,从任意一个事件分叉出新会话。cost,按模型按回合的账单,精确到每一次调用。
第二个约定,证据驱动的完成契约。
只要这个会话动过可能改文件的工具,模型想宣布完成,就必须先调用 verify 跑验证,测试也好构建也好,跑通的证据落盘存档。没验证就想收工,打回,最多两次。还拿不出证据,就以未验证状态结束,退出码 3。
退出码这个事是给 CI 的。流水线不关心模型说了什么,它只认 0 还是 3。这是 keel 和我见过的所有 harness 最不一样的地方,完成不是一个语气,是一个可以写进 shell 脚本的数字。
第三个约定,策略先行。
所有工具调用先过一道声明式策略引擎再执行。路径黑名单拦住 .env 和私钥,读写都拦。命令黑名单、人工审批、只读模式,会话花费超了预算自动熔断。而且每个判定本身也落成事件,审计的时候能看到每一次放行和拒绝的理由。
最后是强迫症部分,零运行时依赖。整个项目三千七百行 TypeScript,npm 装下来一个运行时依赖都没有。三种线协议,OpenAI、Anthropic、Responses,环境里 export 了哪家的 key 它就用哪家,一把 key 都没有的话,一个 KEEL_MOCK=1,内置的确定性 mock 模型能跑通全流程。
写到事件流这块,必须插播一段我自己的震撼。
前段时间我在研究 Apache 孵化器里的一个项目,叫 Maka。Apache Maka,孵化中,五千七百颗星,定位是高性能 agent workspace,README 里有句话,说它保存着它做过的一切事情的完整记录。
再往下翻,它的设计哲学就一行大字,The log is the runtime。
日志即运行时。每个模型消息、每次工具调用、每个权限决定、每次终止,都是一个只能追加的 RuntimeEvent,界面、下一轮 prompt、崩溃恢复,全是这条日志的投影。
我当时就愣住了。
这不就是 keel 吗。
我那条 events.jsonl,我的纯推导,我的 fork 和 replay,一个在 Apache 孵化器里长着近五千个 commit 的项目,和我这个几千行的小仓库,在同一个地方下了同一个判断。agent 运行时的真相源,应该是那条只能追加的日志。
两个互不相识的团队收敛到同一个设计,这个信号比任何 star 数都硬。它说明这条路的共识已经形成,剩下的只是谁在哪个场景把它做透。
当然差别也很大,而且差得很有意思。
Maka 是一艘巨舰,桌面端、终端、CLI、评测,全部是同一个执行核心的瘦客户端。它证明自己的方式是跑分,README 写着,我们发布每一次运行,同样的模型、同样的官方验收器、完整的逐任务记录。它把 harness 的好坏变成一张公开的成绩单。
keel 是个反方向的物种。它不想成为你的工作区,它只想守住一次任务的可信完成。它证明自己的方式不是跑分,是运行时契约,会话里的每一次完成,都必须有当场落盘的验证证据背书,否则退出码直接告诉 CI 这事没完。
一个管统计,一个管个案,一个给你看平均分,一个让你当场验货。我想了半天这俩打不打架,结论是不但不打,还怪互补的。
然后按惯例,泼自己冷水。
keel 现在是 0.3.0,仓库里放了一份验证记录,七十六个测试全绿,step-5-preview 真实模型的全链路验证过程也都在,但同一份文档里明明白白写着,当前证据只支持受控试运行。翻译一下,我自己敢拿它跑小任务,不敢把它塞进任何人的生产流水线。
0 star 的仓库说这种话反而轻松,反正也没人指望我。
接下来想干嘛,分两层说。
keel 这层,三个方向。一是把完成契约打磨到敢默认开启,fork 之后证据失效、子进程清理这些边界已经补了回归,还欠真实负载的检验。二是学 Maka 的评测文化,harness 这个东西不跑分就没有话语权,我想把 keel 放进公开基准里跟同行比,成绩不好看也比藏着强。三是把 fork 玩出花,CI 里的重试不该从头再来,该从失败的那个事件分叉出去,省 token,还能顺便做归因。
我自己这层,是个更大的盘。Maka 是我今年主攻的 Apache 项目,从自提 PR 做起,路径笨但清楚。而 keel 对我的意义,除了项目本身,还是一张入场券的练兵场。agent runtime 这个东西,你只是用过,和你自己从头写过一遍,去上游提 PR 的时候说话的分量完全不一样。
自己写过 harness 的人,才知道崩溃恢复为什么要区分已提交和未提交的日志,才知道 fork 为什么绝不能回滚工作目录,才知道一个退出码背后要处理多少种中断。知道这些之后,给 Maka 提的每一个建议才都落在点上。
这就是我的小算盘,keel 替我把地基打实了,Maka 那头,路会越走越宽。
写着写着想起一段旧事。
十五世纪的威尼斯商人搞出了复式记账法。每一笔交易记两遍,借贷相等,账本对不上就是有问题。这套笨办法沿用了六百年,成了整个现代商业文明的信任地基。
agent 时代需要的就是同一种笨办法。模型会幻觉,会忘事,会拍胸脯,所以它的每个动作都得记两遍,一遍进日志,一遍进证据,两边对得上才算数。
日志是它的账本,证据是它的签收单。退出码?那是它的信用分。这么说有点肉麻,但理是这么个理。
写记忆那篇的时候我说过一句话,你记得什么,你就是谁。这篇想补一句,你怎么记账,别人就怎么信你。
keel 在 GitHub 上,叫 keel-agent,MIT 协议。零依赖安装,没有 API key 也能玩,KEEL_MOCK=1 一个环境变量,事件溯源、验证契约、回放分叉全流程体验一遍,不花一分钱。
当然,0 star 的项目,你就是它的第一个用户。不亏。掌声和 bug,都从你这儿开始。
以上,既然看到这里了,如果觉得写得还行,随手点个赞、留个言吧,想第一时间看到后续也可以订阅我的 RSS~
谢谢你看我的文章,我们,下次再见。

