六个月纯用AI Agent写代码的实践

今年二月,我给自己定了一个规则:我不再手写代码。我已经按照这个规则生活了六个月。

这个系统活在我脑海里

回到2024年,在人工智能出现之前,我的超能力是了解整个系统的运作方式,尤其是各个组件之间的接口。

如果有人带着想做的功能或想修复的bug来找我,我通常能准确指出重要的代码行,告诉他们需要修改的地方。我还记得那些奇怪的决策为何存在,以及哪些假设从未被写下来。

这些知识是我在代码库中数月甚至数年积累下来的。这是来之不易且无价的。它让我能够快速且更安全地构建功能。

代价是我必须跟上所有内容。随着越来越多的人参与,我花越来越多的时间阅读修改,只是为了维持那个心态模式。

最大的代价是打字。每次我想做点什么,脑海里都能看到代码。我就是打不够快。

打字速度只是问题的一部分:功能几乎从来不是一次编辑。即使是小改动,也跨越多层,涉及处理器、模式、测试和文档。这些编辑并不平等:糟糕的处理器可以被恢复,但错误的迁移可能会留下混乱。

所以手写代码意味着要安全地带着一个决策通过它接触的每个地方。

副驾驶自动补全立刻帮上忙:文档评论变成了初稿,虽然经常出错,但总比编辑空白文件强。光标的标签完成功能帮助更大。模型明显进步得很快。

Claude代码改动了很多。我只描述一次变化,代理就会同时编辑一堆文件。

因此,我打字的次数大大减少。但打字减少并不意味着工作量减少:我会阅读模型生成的每一个变化,将其与我脑海中想要的状态匹配。

代理仍然会经常出错,做出不必要的修改。渐进式工作让他们保持正轨。这意味着我需要手工编辑部分生成的代码。毕竟,我仍然要对每行合并的行负责。

模型不会被追究责任。

然后,今年年初,模型几乎同时变得非常出色。GPT-5.3和Opus 4.6突然能够以更少的引导处理更大幅度的变化,结果终于足够好,可以继续发展。

所以二月份我定了个规矩:不再手动写代码。如果有代理卡住了,我就不能自己完成代码。我必须找出代理缺失了什么——然后自己修正。

我不是靠读写代码才学会的。我是通过写大量代码、运行代码、看到失败、修复、再做一次才变得精通的。

毕竟,AI代理只是软件。我不会通过阅读提示指南来理解它们。我必须在实际工作中使用它们,看看它们哪里失败了,修改提示词、工具或环境,然后再试一次。

规则迫使我去做那些重复次数。

我破解了一次,持续了三分钟。我打开代码写了几行,感觉非常好。我很想念这种感觉。直到我意识到自己还有多少字要打。我放弃了。

一名特工变成了十几个

一旦我停止自己输入代码,我开始发现这些空闲时间。我会给客服一个任务,然后在它工作期间我无事可做。

我没有等待,而是让另一个特工去做别的事。然后我又做了一次。

我并不是有意构建一个并行系统。我只是打发任务之间的时间。我有多动症。我分心了。

很容易想象如果同事共用一个开发机会发生什么。现在想象他们彼此不交流,同时工作。

那是我第一次并行设置。代理们更改相同的文件和Git状态,安装依赖,争夺端口,并让进程继续运行。我还得协调每个代理何时测试、推送或部署。

更糟的是,我经常得等到运行时间最长的代理,其他人才能继续。我启动了更多代理以避免等待,并不知怎么地创造了一种新的等待方式。

我问了朋友和同事他们是怎么应对的,大家都有解决办法。

worktrees(工作树)最先出现。每个代理都有自己的检查和分支,源冲突大多消失了,但worktree只解决了Git部分。代理们仍然共享数据库、端口、进程和机器的其他部分。

所以人们就用AGENTS.md使用随机端口,创建一个临时数据库,不动其他代理的进程。每一次冲突都变成了另一条指令,代理们浪费了上下文,想办法不互相踩踏,而不是完成任务。

容器越来越近:不同的港口、进程和本地州。但边界是有漏洞的:无论我的笔记本能到达什么,容器也可能触及。错误命令的爆炸半径未被控制,所以我仍在批准命令。

最糟糕的是,我的笔记本必须保持清醒状态。如果我关闭它,所有工作都会停止。

我合上了笔记本电脑。工作还在继续。

到那时,我已经加入了exe.dev.我们做几秒钟内就能启动的Linux虚拟机SSH和HTTPS已经设置好了,所以把代理从笔记本上移开是自然的下一步。

每个任务都有自己的机器。我可以合上笔记本,走开,工作依然继续。

但现在我遇到了一个新问题:我如何可靠地为我想让代理处理的任何项目建立一个完整的开发环境?

Three attempts at a startup script: fresh VM each time, looping until the environment came up clean

所以,我坚持不写代码,让Claude写一个启动脚本。我告诉它我想要什么,并指示它循环直到一切正常。它安装了我们的工具链,克隆了仓库,配置了Claude代码和Codex,完成了将新虚拟机转变为开发环境所需的一切。

然后它运行了验证循环:调出一个新盒子,运行脚本,看看哪里出了问题,修复脚本,然后再试一次。

代理盒子能用,但每个代理仍有自己的多工会话。我最终开了十几个终端窗口,只为看看每个代理在做什么。我不得不在它们之间跳来跳去,找出哪个代理完成了,哪个卡住了,哪个需要我帮忙。

我需要一个地方来管理所有这些。

所以我建造了botd.

我给它定了三个规则。第一,它必须运行在笔记本电脑以外的地方(我关闭电脑后,客服应该能继续工作)。第二,莫比尔必须是一流的.管理座席不应该要求坐在终端前。

第三,必须保留每一次对话,这样我才能回顾所有座席,了解他们卡在哪里,哪些指令有效,哪些问题反复出现。

botd配置和取消代理盒子,驱动代理到下面,跟踪每项任务。它显示哪些代理在工作,哪些卡住了,哪些在等我。我可以用手机或笔记本查看对话,发送后续指示,并查看差异。

我不再管理十几个终端会话,而是在一个地方管理工作。

如果我必须批准每一个工具调用,这一切都不管用。那样我又会被排进队列。每个代理都运行在一个隔离的一次性虚拟机里,所以我让它以YOLO模式运行。

它可以运行bash命令、安装包、启动服务,并更改所需的任何内容。一个被破坏的环境只会让我失去虚拟机。

但一个只能接触自己虚拟机的代理就没什么意思了。我仍然希望代理能读取日志、从 Git 拉取、调用 Anthropic 或 OpenAI,并在 Stripe 中检查内容。

真正的风险就在于这种访问,而虚拟机没有对它做任何绑定。代理读取不受信任的内容,可以被提示注入;但无论它能到达什么,注入的代理都可能泄露或损坏。

我知道自己还没填补漏洞。所以每个外部访问都被问到同样的问题:通过这种方式最坏的情况会是什么?读取大多通过了:代理获得了只读权限。

写入则没有,所以写权限仅限于测试环境,最坏的情况是测试数据损坏。

我也不想虚拟机内部的凭据.其中exe.dev 积分代理通过代理发送请求,代理添加凭证,代理在未见秘密的情况下收到响应。

一切都过去了。我还是不想要。

高峰期我同时运行大约二十台虚拟机。并非所有虚拟机都处于活跃状态。有些任务启动后几周未动,最终因认知负担过大且工作不够紧急或重要而被放弃。

在那个规模下,验证很糟糕。我无法手动重建每个分支,重跑测试,自己检查应用。但在他们隔离的框架内,代理可以运行测试,触发完整的CI套件,启动应用程序,并通过浏览器驱动。

他们会给我发完成工作的截图。

但代理仍在给自己的作业评分。如果它误解了我的需求,可能会做错东西,写错测试,然后自信地告诉我一切都通过了。

这就是为什么能够打开运行环境很重要。我可以自己用它,端到端驱动新界面,确保它按我想要的方式工作,而不仅仅是代理说的那样。

然后是审查。当我自己写代码时,等到准备审阅时,我已经理解了变更。毕竟,我一路上就做了决定。有了代理,整个差异会一次性出现。

虽然有通过测试和截图,但代码依然陌生。当几个代理同时完成时,我脑中有一排完整修改的队列,才能合并它们。

A queue of finished agent branches, each with passing tests and screenshots, waiting on one reviewer

拥有其他代理人审查代码效果出乎意料地好。他们偶尔会发现真正的bug,而且成本也足够低,可以跑几次审核。但我不能仅仅因为代理批准就合并某个东西。

我仍然需要理解这个变化。我仍然负责合并的代码。

有时候我也能理解。测试是绿色的,截图看起来不错,界面也符合我的要求,连模式也很好,代码也得到了所有审核代理的批准。

不过我还是扔了。

也许没人需要它。也许它引入了第二种方式来做我们已经支持的事情。也许一个小便利,增加了我们多年背负的复杂性。

工具可以告诉我这个改动有效。但他们无法告诉我是否值得加进系统。

卸载某样东西比发货难得多。海勒姆定律发挥作用:一旦足够多人使用系统,总有人依赖每一个可观察的行为,甚至是你从未打算成为合同的行为。

移除某样东西会破坏用户、脚本和你不知道存在的工作流程。

运输变得容易了。决定运输什么变得重要。

并非所有代理都写代码

到目前为止,一切都围绕运货代码展开。但是我们运营的一些最有用的代理根本不要发货代码。

值得停下来思考“代理人”到底是什么,因为这个词听起来比它应得的更沉重。代理人是一个带有工具的循环模型.给模型发消息;如果需要工具调用,运行工具并返回结果;重复。

这就是全部。

模型才是让循环具备功能的关键。在真实电脑上试试,它能安装缺失的部分,当你的 grep 有不同标志时自动调整,然后一直运行直到完成。

循环永远不会改变。工具决定代理可以成为什么样的人。开发代理需要一台完整的计算机:shell、编译器、浏览器,还有安装自由。如果这些被限制,代理就没用了,你又回到了批准每一个操作的状态。

我的起点是:给代理我会给开发者的东西。好的开发者工具证明是好的代理工具。至于最好的代理工具最终是否真的是开发者工具,时间会证明一切。

而真正需要担心的不是单一工具。而是组合。私人数据、不可信内容、外部通讯:任何两种都可以管理。三者合一,就是你的秘密如何走出门外。

这就是西蒙·威利森所说的致命三重奏.所以我不会盲目地贬低工具:我会隔离环境,观察组合。

这是第一个例子:调查。当客户报告问题时,我会用一个提示,简直令人尴尬:

“客户报告:请利用ClickHouse日志查明发生了什么。”

逐字逐字的重要性。如果我总结报告,客服会继承我的理解——以及我的盲点。根据客户自己的话,它查询日志,读取相关代码,重建实际发生的情况。

然后决定权在我手里。有时我会询问选项,然后选一个。有时证据显示按预期工作,修复方案是文档或邮件,而不是代码。无论如何,我都是根据证据做决定,而不是从bug报告中猜测。

第二项任务:攻击。我们的红队特工只有一个指令:尝试入侵我们的系统。

而且它成功了。它找到了我们以为已经限制的开放网络路径,并向我们展示了它们仍然可达的过程。我们在外界发现之前就修补了它们。

这比理论上的漏洞列表更有用。它基于我们依赖的一个假设,并将其与运行中的系统进行测试,结果假设是错误的。

第三个任务:观察。部署令人害怕,但不部署更糟。我们是分批部署的,制定完美的继续规则几乎不可能:生产以奇怪的方式失败。

所以雅典娜每次部署都负责看护。它读取差异、指标和日志,并监控部署过程。在一次部署中,它发现了一个问题,调查后发现是基础设施问题,而不是新代码。

它没有盲目停止所有工作,而是继续向其他机器部署。

A deployment rolling out in waves; Athena flags an infrastructure issue mid-rollout and keeps the wave going

我能做得更好吗?我现在不确定了。雅典娜比我更勤奋。它不会分心,不会不耐烦,也从不停止关注。它不知疲倦。

在代理编写代码之前,先对系统进行工程设计

代理人现在几乎可以瞬间设计和构建整个系统。设计甚至可能很好。但如果我只是接受它,我知道它是如何工作的吗?它在哪里出了问题?

它在哪些地方被偷工减料了?我同意了哪些权衡?

那时,我继承了一个全新的遗留代码库。这就是氛围编码。

代理工程首先与代理一起处理系统:架构、接口、约束和权衡。代码到来时,我才明白即将拥有的东西。

智能工程没有唯一的答案。每个人的工作方式不同;每个模型擅长的事情都不同。关键是重复:多做,多提出要求,多舍弃。你会学会适合自己的方法。

我们对运行软件团队的了解现在得到了放大。从测试开始。当代理编写和重写实现时,镜像实现的单元测试会随之循环。行为测试、合同测试和属性测试现在更为重要:定义系统的不变量,明确哪些必须保持真理,同时底层代码可以自由变化。

现在游戏很大程度上是迁移。代码修改成本低,但系统会携带状态:数据、运行进程、中转用户。从一个设计到另一个设计而不丢弃这些仍然很难。

工作是风险管理和迁移。

代理人复制代码库中找到的内容.好的模式会放大。坏的模式会放大得更快。工程卫生比以往任何时候都更重要:你容忍的每一个模式都成为接下来百个变化的模板。

而且它是个性化工具的黄金时代.写新工具不需要领域专家。那就写一个新的工具吧。强制执行模式,编码课程,让错误变得不可能。定制工具的边际成本已经崩溃。

同伴代码审查已经死了。我们不做代码审查exe.dev:我们合并我们认为应该合并的代码.重要的审查会更早进行:设计讨论、合同制定、验证。

等差异存在时,决策已经做出。

这里有一个最近的例子。雪莱是我们寄送的编码代理exe.dev虚拟机。我希望它的工具是异步的。

第一种设计让代理决定哪些命令应该在后台运行。这带来了大量边缘情况:大输出、长时间运行的任务、被阻塞的代理,以及代理必须提前预测所有这些。

如果我是vibe编码,我会接受这个设计,让代理处理那些边缘情况。也许它能处理这些问题。也许不会。我其实不太清楚。

相反,我们不断讨论这个问题,最终找到了一个设计,完全消除了大部分边缘情况。每个命令都遵循相同的路径,并在六十秒后自动进入后台。

我一时想不起这个例子。我请一位代理在我所有的对话中搜索我的问题实质性改变了架构的案例。它返回了原始请求、最初的设计、我提出的异议、设计的变化、最终提交以及原始会话的链接。

这就是为什么保存每一次对话都很重要。历史不仅仅是档案。我可以查询它来理解我是如何工作的。

Botd已经死了;Botd万岁

这个故事还有一部分。botd本月死了。它在自己的重量下崩塌了。

这其实也不奇怪。botd完全是vibe编码。我一行代码都没读,也没怎么注意它的架构设计。核心问题确实是:让每个模型家族都通过自己的原生线束(Claude Code、Codex等)来驱动,同时掩盖它们的差异。

这正是架构问题所在,而我当时没注意它。

它帮我交付了大量工作,但最终变成了我之前描述的那个:一个全新的遗留代码库,我继承了它,却没有理解其中所有决策。架构是探索性的,其中一些决策即使是微小的改动也意味着必须重做系统中的大部分内容。

我想在Shelley之上构建,以便更好地控制线束;botd是借来的时间。它在我退休之前就死了。

但有个转折:我之前描述的那个搜索从未发送到botd。所有历史都在SQLite里,所以我指向另一个代理,结果还是得到了分析。工具死了;数据没死。

刚开始时,我的优势是系统活在我脑海里。六个月后,老实说我失去了一些深度。我对每个系统都没有同样的逐行熟悉。

但我可以向经纪人提问详细问题,利用他们来挖掘我需要理解的内容。工作不再等我完成一件事再开始另一件事。

这六个月里我发布的作品比我职业生涯中任何阶段都多。我也失败过更多:被遗弃的虚拟机、死胡同的设计、一整套崩溃的工具。失败成本低,所以我能负担得起很多——这就是我学会什么有效的方式。

这和教会我编程的循环一样,升级了一个层次:写、运行、失败、修正。我以前会迭代代码。现在我会迭代提示、设计,甚至整个功能。

以前要花一周时间的重写,现在却要花一次对话。而且理解是不断迭代的另外:每一次对话都帮助我更好地理解和内化这个系统。

我停止了手写代码。我没有停止工程工作。

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论