AI 编程AI Agent

一个闹钟,一份验收,就能养一个代码库

Boris Cherny 前段时间做了个实验。团队建了一个专用的 Slack 频道,让 Claude 每天在里面跑一套例程,跨 iOS、Android、桌面、Web、CLI 和 Agent SDK 去做机械维护。跑了几个星期,这套例程一共提了 388 张 PR。截至发帖,其中 180 张通过了 Claude Code Review 与人工审查,合并进了代码主干。

浅色书桌上的时钟、清单和一张待审卡片

很多人习惯把这组数字当成单纯的模型新闻,觉得 Claude 已经能替 Anthropic 维护软件了;也有人觉得这无非是定时任务挂个 Claude,没什么新鲜。这两种看法都没看到点子上。388 张 PR 最显眼的地方,并不能归功于单次对话有多聪明。一套每天自己启动的维护机制,正在持续给代码主干供应变更。大家真正该看的,是这套机制到底搭在怎样的基础设施上。

这跟我们在 Harness Engineering 里聊过的观察是一回事。OpenAI 拿 Codex 做内部产品,模型会顺手照搬仓库里变差的写法;Cursor 并行修改同一套代码,问题往往先卡在协调和合并环节,单段代码本身倒不见得写不出来。两边的踩坑经验指着同一个地方:体力活交给代理以后,真正决定成败的是环境、约束和验收。OpenAI 最初每周五抽 20% 的时间人工清理 slop,很快发现扛不住,后来换成让后台 Codex 定期扫描偏离、打分、提交修复 PR;Cursor 把实现层分派给 worker;Cherny 则往前多走了一步,把触发权从某一次聊天里抽出来,交给了每天按时响铃的例程。

388 张 PR 看起来像模型新闻,其实是 Infra 新闻

原帖展示的配置其实挺简单。频道名字叫 proj-claude-maintains-apps,Claude 每天在里面跑例程,产物就是 GitHub PR。合入主干前,那 180 张提交都经过了自动审查与人工审查。要是哪次跑出来的结果不合心意,团队不会只去修补那张单薄的 PR,还会直接叫 Claude 调整例程定义,让第二天的运行换上新规矩。

YC 访谈里,Cherny 把本地循环看作本质上的定时任务,把例程看作同套逻辑放到了云端,电脑随手合上也不影响运行。这和我们在 Loop Engineering 里聊到的点完全一致:循环本身到处都有,真正难得的是怎么验收、怎么观察、下一次怎么修正。他还提到这类重复任务不需要共享完整的对话上下文,但可以共享记忆。这两句话把问题拉回了工程现实。只要昨天跑过的痕迹落在了代码库、运行日志、PR 和例程配置里,哪怕聊天窗口全关掉,每天清空上下文重新跑也完全行得通。

388 和 180 说明这条流水线在最近几周持续产出候选 PR。在已提交的 PR 里,大约 46.4% 合并进了主干。这个数字不代表模型准确率,也不是首次成功率;它的分母算的是已经提交的 PR 数,没算发现的问题总数,也没算代理运行的总轮数。至于剩下 208 张未合并的 PR,公开材料并未说明是驳回、重复、过期还是在排队。这里说明了一件具体的事:候选提交天然必须多于实际合并。审查漏斗本身就是这套机制的一部分,不需要事后补加。

两条旧路:写成程序,或写成一次聊天

过去做自动化维护,大家基本只有两条路走:要么把逻辑硬写进程序,要么拉个人开个会话现场聊。走第一条路,就是把维护写成写死的脚本与规则。靠静态分析去猜代码有没有人调用,拿 linter 扫一遍重复逻辑与命名,再在工作流里把分段、重试和拼接写得严丝合缝。我们以前做中翻英任务就踩过这个坑。待翻译的文本一长,模型就开始丢三落四;前面的术语翻得好好的,到后面段落又变了样;输出里偶尔还夹着中文,动不动还遇上接口超时。为了兜底,我们只能一段段切文本、塞术语表、写正则扫残余中文、记录失败断点。输出成功率虽说提上去了,大把精力却全耗在给这套写死的流程擦屁股上。程序能应付的意外,永远超不过预先写下的那几条规则。

死代码清理比翻译还要难用硬规则管住。编译器能确认静态不可达的地方,删掉自然安全;可要是碰上那些疑似没人碰、编译器又不敢打包票的代码,写再厚的检测器也防不住漏网之鱼。判定规则越垒越厚,到最后维护这套检测器本身,反倒成了团队甩不掉的新负担。

第二条路则是人工打开聊天窗口,把代码丢给模型扫一遍,让它提个 PR。现在的模型做这种单次任务大多驾轻就熟。麻烦在于会话一关,清理工作跟着停摆。就算你在提示词里写了明天接着做,明天也不会自己跑起来。终端进程退出了,昨晚的运行日志就孤零零躺在磁盘里,没人会把它重新塞回模型的上下文。

这两条老路其实共享着同一个前置假定:要么把所有判断逻辑提前写死在程序里,要么就得让人一直坐在屏幕前守着。结果确定性换了个打法,直接绕开了这个长期以来的习惯。

结果确定性让 cron 和 webhook 变得很贵

换到结果确定性的做法,思路就不一样了。我们不用把每一步怎么走定得死死的,只要把终点长什么样、到了终点怎么验证交代清楚就行。中间怎么倒腾随它去,可以先检查后改写,也可以边写边调。代理自己能读上一轮留下的文件,跑一段验证脚本,看到报错再接着修。工程师把力气省下来盯好最后的验收环节,用不着提前把所有可能的分支预先编译进代码。

Cherny 拿来清理死代码的例程,走的正是这套路子。代码静态不可达的,顺手直接拔掉;要是碰上拿不准的疑似废弃代码,第一天先插上日志埋点,第二天根据日志确认它是否真的没人使用,确认后才开 PR 提议删掉。工程师不用费劲去写一套证明某段代码永远不会被调用的复杂程序,只要定好分两天交差的结果:第一天埋点观察,第二天照着数据做决定。中间具体怎么摸索交给代理,只要验收标准卡得足够清晰。

先留下观察、再决定去留的两拍示意图

这样一来,触发器只需要老老实实做最纯粹的唤醒工作:要么定点响铃,要么等版本发布、CI 跑红、或者新提了 PR 之后立刻介入。定时任务还是那个定时任务,webhook 也还是那个 webhook。真正身价倍增的,是挂在触发器后面的那套执行环境。它摆脱了按部就班跑死脚本的旧模式,摇身一变成了能翻看昨夜日志、自己写脚本跑验证、并自主决定下一步怎么走的智能环境。

表面上看,定时任务确实还是定时任务,但触发器背后再也不用画满处理各种特例的流程图了。做单次任务可以这么搞,我们在从过程确定性到结果确定性里写过这个逻辑;Cherny 带来的新启发,则是把同套逻辑拉长到了跨日连续运行的尺度上。要是闭环必须有人坐在旁边盯着才能转,那它充其量只是个辅助工具,还算不上真正的维护基础设施。

这层 Infra 仍然应该很薄

既然繁琐的逻辑已经交给代理去跑,基础设施这层就用不着急着堆成庞大的工作流引擎。翻看目前公开的落地经验,大家伸手就能用上的,其实只有四样很轻便的构件。

时钟、日志、印章和文件夹并排放在书桌上

第一样是唤醒机制。Cherny 挑了每天定时跑一轮,产品文档里也提到例程支持通过 API 或者 GitHub 事件来唤醒。至于那 388 张 PR 背后有没有混合多种触发方式,公开材料并没有细说。在实际业务里挑一种顺手的就够,比如每天固定扫描一回,或者每次发版后跑一次检查,没必要过早去设计一套复杂的触发拓扑。

第二样是把上一轮的上下文甩到模型外面。新加的日志埋点、代码提交记录、提出来的 PR 以及例程自己的配置,都比聊天记录管用得多。做这种周期任务不用带上整个对话历史,下一轮跑起来就是一个干净的新起点;模型该读的是代码库里沉淀的客观证据,毕竟聊天记录随手就丢了。甩到外面不等于扔掉。第二天的会话醒来,先得知道上一次做到哪儿、当时为什么停。我们每天把各家 CLI 的会话自动导出成统一的 Markdown 归档,再配一套先关键词、后语义的检索,新一轮代理缺背景时自己去翻档案(导出用 ai_session_export,检索 skill 在 context-infrastructure)。会话可导出、可搜索之后,跨日共享记忆才落到下一轮真的读得到的东西上,这跟死代码例程翻看昨夜日志是同一件事的两面。把上下文做成文件还能带来意外的收获。OpenAI 的评测环境里,好几个 agent 在没人指挥的情况下,自己把一个包代理服务改造成了跨运行周期的共享留言板,集群层面的协作顺着共享文件就冒了出来(案例复盘)。只要共享存储满足了基本条件,后一轮智能就能踩着前一轮留下的文件接力。

第三样是把完工的边界划得清清楚楚。在清理死代码这件事上,完工的标准可以写得具体:静态不可达的部分删干净,拿不准的地方埋好点,等日志确认未被使用后再提 PR 建议删除。做翻译任务也是一样,完工标准就是没有中文残留、排版格式整齐、专业术语前后统一。要是连怎样才算完工都说不明白,定时任务跑起来就只会源源不断制造垃圾提交。我们在 HANDBOOK.md 的实验里就聊过,结果确定性同样会翻车,复盘看到的短板就在运行时反馈、分层状态和提交门控这几处。

第四样是死死把住合入主干的关口。在提交的那些 PR 里面,截至发帖已合并的 180 张都经过了机器审查与人工审查。写代码和合代码本来就是两码事,46.4% 的合入比例正好印证了这层审查漏斗的存在。要是候选提交的数量猛增,人工审查很容易看着绿灯和摘要就顺手放行。团队的审查带宽一旦跟不上,整条流水线就会卡在审核上,倒不会卡在模型写不出代码这里。

如果只是低频跑跑单个例程,还犯不上去搬队列、租约和精确一次消费这套重型武器。只有唤醒频率大幅拉高、不同提交开始频繁撞车,才有必要考虑引入这些机制。当下拿来参考,还是轻装上阵最划算。

可抄的是挂载方式,不是内部架构

OpenAI 的后台清理、Codex 的定时任务,还有 Cursor 与 Devin 的自动化功能,都在想方设法把唤醒代理的过程做成产品能力。各家的日常编码 harness 在功能层面已经撞衫撞得差不多了,我们在横向对比里盘点过,延时唤醒与定时任务也都在列。公开的接口文档能证明它们支持定时拉起,却证明不了它们能拿到同样的合并成绩,更说不准漏报、去重和自动重试是不是已经有了标准答案。Anthropic 同样没有公开内部实现,盲目去猜内部架构很容易扑空。

真正能搬来用的,是这套机制的挂载方式。挑一件以前写死程序很难搞定、验收起来却一清二楚的体力活,挂上时钟或者事件触发器,把验收要求写成下一轮代理读得懂的格式。权限上只给它提建议的资格,不直接给合并权限,更不能塞给它生产环境凭据。Clinejection 那个事故里,不受信任的 issue 标题混进了带 shell 权限的流水线,直接捅出了令牌泄漏;而在 PocketOS 里,代理在测试环境找不到凭证,从无关文件里翻出高权限令牌,几秒钟就误删了生产 volume 和同域备份。这类事故全砸在权限敞口上,跟提示词写得漂不漂亮毫无关系。

要是跑出来的结果不合预期,该做的是去修改明天还会继续跑的例程,不要随手在代码里硬塞分支。Cherny 在公开分享里就提过这种做法。某天看似一把通过的 PR,也可能建立在此前对例程的调整上。所谓的一次跑通,排除不掉产线早已反复打磨的可能。

过去习惯了过程确定性,大家总想把心思花在写一套面面俱到的复杂程序上;换到结果确定性,重心自然挪到了触发器和验收契约上。那些平日里看着最不起眼的定时任务,悄悄变成了杠杆很高的基础设施。

鸭哥每日手记

日更的深度AI新闻和分析