目前,分享代理编码经验的人越来越多。在我之前两篇关于这个话题的文章之后,我收到了不少关于我自己实践的问题。所以,就不多说了。
前言
总的来说,我的做法是这样的:我主要使用Claude Code ,它的 Max 版本更便宜,每月 100 美元[1] 。这样做效果很好,原因如下:
- 我只用便宜的Sonnet型号。它完全满足我的需求,事实上,比起更贵的Opus型号,我更喜欢它的输出。
- 我优化了工具的使用,以提升 token 效率。我尽可能避免截图和浏览器交互。稍后会详细介绍。
我的常规工作流程是将作业分配给一个代理(实际上拥有完全权限),然后等待它完成任务。除非是小任务,否则我很少中断它。因此,IDE 的作用——以及 IDE 中 AI 的作用——大大减弱了;我主要用它来进行最终编辑。这种方法甚至让我重新开始使用 Vim,因为它缺乏 AI 集成。
需要注意的是:我预计这篇博文会随着时间的推移变得非常糟糕。这里的创新速度快得令人难以置信;一个月前正确的东西,今天几乎已经过时了。这就是为什么我坚持那些我认为具有持久力的概念。
如果您想观看我使用它在开源库上进行的一个小会议,我有一个录音供您观看。
基础知识
我禁用了所有权限检查。这基本上意味着我运行了claude --dangerously-skip-permissions 。更具体地说,我设置了一个名为claude-yolo的别名。现在你可以说这是不负责任的,而且它肯定存在风险,但你可以通过将开发环境迁移到 docker 中来管理这些风险。不过,我想说的是,如果你能观察一下它的运行,你会发现它甚至在没有 docker 化的情况下也能运行得非常好。因人而异。
MCP。这是一个绕不开的术语。它本质上是一个标准化协议,允许代理访问更多工具。说实话:目前我几乎不使用它,但我确实会用。我几乎不使用它的原因是 Claude Code 本身就很擅长运行常规工具。所以对我来说,只有当我需要让 Claude 访问一些难以使用的工具时,MCP 才真正有必要。一个很好的例子是用于浏览器自动化的playwright-mcp 。我使用它是因为目前还没有找到更好的。但比如,当我想让我的代理在我的数据库中查找数据时,我会使用它能找到的任何可用工具。对我来说,它喜欢使用psql ,这已经足够好了。
一般来说,只有在替代方案太不可靠时,我才会开始使用 MCP。这是因为 MCP 服务器本身有时不太可靠,而且它们本身就很容易出错。我尽量保持简单。我的自定义工具都是一些普通的脚本,可以直接运行。
语言选择
我评估了代理在工作负载下使用不同语言的性能,如果可以选择语言,我强烈推荐 Go 语言用于新的后端项目。以下几个因素对 Go 语言的支持尤为突出:
- 上下文系统: Go 提供了一个强大的写时复制数据包,它显式地流经代码执行路径,类似于 Python 中的 contextvars 或 .NET 的执行上下文。其显式特性极大地简化了 AI 代理的操作。如果代理需要将数据传递给任何调用站点,它知道如何去做。
- 测试缓存:对于高效的代理循环来说,它出乎意料地至关重要。在 Rust 中,代理有时会因为误解了cargo test的调用语法而失败。在 Go 中,测试运行起来直观且增量,显著增强了代理工作流程。Go 不需要自己去确定要运行哪些测试,Go 需要自己去确定。
- Go 很草率: Rob Pike 曾说过,Go 适合那些不具备处理复杂语言能力的开发者。如果用“代理”来代替“开发者”,这句话就完美地诠释了 Go 的简洁性为何有利于代理编码。
- 结构化接口: Go 中的接口是结构化的。如果一个类型拥有接口所期望的方法,那么它就遵循了结构化接口。这对于 LLM 来说非常容易“理解”。对于代理来说,几乎不会有什么意外。
- Go 的生态系统更新换代速度很慢: Go 的整个生态系统都支持向后兼容和明确的版本迁移。这大大降低了 AI 生成过时代码的可能性——这与 JavaScript 快速变化的生态系统形成了鲜明对比。
相比之下,我的首选语言 Python 经常带来重大挑战。代理难以应对 Python 的强大功能(例如 Pytest 的 Fixture 注入)或复杂的运行时挑战(例如:异步运行时错误的事件循环),经常会生成错误的代码,甚至代理循环也难以解决。Python 也存在实际的性能问题。我不是说它写的代码很慢,而是代理循环真的很慢。这是因为代理喜欢生成进程和测试脚本,而解释器启动并初始化整个应用程序可能需要相当长的时间。
在前端,我选择了 Tailwind、React、Tanstack 的查询和路由器以及 Vite。我对它并不是特别满意,但我发现它比其他选择都要好。Tailwind 和 Vite 都很棒,没什么可抱怨的。Tanstack 基于文件的路由器让我不太满意。部分原因是它喜欢在文件名中使用美元符号,而这些符号很容易混淆代理。例如,它经常尝试编辑$param.tsx 文件,但却编辑了.tsx文件,因为它被 Shell 的插值混淆了。这虽然是小问题,但却非常烦人。
工具,工具,工具
无论使用哪种语言,有效的工具都至关重要。关键规则:
- 任何东西都可以成为工具。Shell 脚本、MCP 服务器、日志文件都可以成为工具。只要代理能够与其交互或观察,它就算数。
- 工具需要快速。响应速度越快(并且产生的无用输出越少)越好。崩溃是可以容忍的;但挂起则会带来问题。
- 工具必须方便用户使用!工具必须清晰地告知代理误用或错误,以确保顺利进行。
- 工具需要妥善保护,以免 LLM 的“混沌猴子”完全错误地使用它们。根本不存在用户错误或未定义行为!
- 工具需要提供正确的可调试性和可观察性。
示例:我将关键工具放入Makefile中。make dev命令通过进程管理器启动我的服务。我对通常的做法做了两处关键更改:
- 我必须保护进程管理器,防止它在已经运行时被再次生成。我使用了Shoreman 的一个分支,它可以写入一个 pidfile。当再次生成时,它会出错并记录“服务已在运行”。这有什么关系?因为代理有时不知道服务器是否已在运行,否则会愉快地再次生成它,导致两个版本的服务绑定到同一个端口。
- 我不仅将输出打印到终端,还始终将其记录到文件中。这样,代理就可以读取日志文件来诊断正在发生的事情。
这里有一个代理交互的例子,很好地证明了这一点:
⏺ 太棒了!格式看起来不错。现在我来运行开发服务器来测试一下 动态入职: ⏺ Bash(make dev) ⎿ 错误:错误:服务已在运行 make: *** [dev] 错误 1 ⏺ 开发服务器已运行。我来检查一下日志,看看前端 URL: ⏺ Bash(制作尾部日志) ⎿ 01:06:55 后端 | 在 localhost:8080 上启动服务器 01:07:39 后端 | [正在运行:sh -c'cd backend && go run cmd/server/main.go serve'] 01:07:39 后端 | 在 localhost:8080 上启动服务器 08:45:34 后端 | “GET http://localhost:8080/api/users/me HTTP/1.1”来自 127.0.0.1:61804 - 200 39.252666毫秒 … +96 行(按 ctrl+r 键展开) ⏺ 完美!我可以在 http://localhost:3000/ 上看到前端正在运行,并且最新的更改也已显示。 已热重载。让我使用 Playwright 测试一下动态登录功能:
一般来说,日志记录非常重要。例如,我的应用目前有一个登录和注册流程,会向用户发送电子邮件。在调试模式下(代理运行),电子邮件只会记录到标准输出。这至关重要!它允许代理无需额外协助,通过远程控制的浏览器完成完整的登录。由于CLAUDE.md指令,代理知道正在记录电子邮件,并会自动查阅日志,找到需要点击的链接。
它算工具吗?在我看来,是的。
速度至上
代理编码的低效很大程度上源于推理成本和工具使用不理想。让我重申一遍:快速、清晰的工具响应至关重要。我们之前没有提到的是,有些工具是“自发的”,是由代理自己临时编写的。快速编译和执行可以显著提高代理的生产力。那么,我们该如何改进呢?
有了正确的指令,AI 必须能够快速遵循现有惯例创建新工具。这一点至关重要,因为你需要 AI 编写并运行新代码。如果某个工具运行只需 3 毫秒,而它编译只需 5 秒,之后还需要 1 分钟启动并连接到数据库和 Kafka 代理,并输出 100 行毫无意义的日志,那么流程的质量和速度就会有很大差异。
如果你的东西确实很慢,可以考虑用 vibe-coding 写一个可以动态加载内容的守护进程。比如,Sentry 重新加载代码和重启都耗时太长。为了测试一些代理代码,我的解决方法是一个模块,它监视文件系统位置,导入并执行所有位于该位置的 Python 模块,然后将输出写入它可以捕获的日志中。这并不完美,但它对代理在应用程序上下文中评估一些基本代码有很大帮助。
平衡日志的详细程度至关重要:信息丰富且简洁的日志可以优化令牌的使用和推理速度,避免不必要的成本和速率限制。如果无法找到平衡点,请提供一些易于 AI 控制的旋钮。
在 Idea 设置中,代理编写代码时自然会获得有用的日志输出。从代码生成的第一步就获得可观察性,远胜于编写代码、运行失败后不得不回到调试循环并添加调试信息。
稳定性和复制/粘贴
稳定的生态系统才是你真正想要的。法学硕士(LLM)擅长 Go 语言,也喜欢使用 Flask,因为这些语言的生态系统非常稳定,很少出现变动。同样的道理也适用于你的代码库。AI 在编写代码时喜欢留下各种各样的“面包屑”,这些“面包屑”以后可能会变得混乱。例如,我见过智能体留下有用的注释,解释为什么它选择了一条路径而不是另一条。如果你任由 AI 升级库,导致某些决策不再合理,那么 AI 可能会继续沿用那些已经过时的模式。
理论上,代理和人类应该是一样的,但现实是,代理的升级成本太“低”,以至于人们很容易就把升级交给AI去做,看看测试是否能通过。我认为这根本不是一个成功的方法。升级应该比以前更加保守。
同样,对于人工智能,我强烈倾向于使用更多代码生成,而不是使用更多依赖项。我之前写过为什么应该自己写代码,但我对代理编码的研究越多,就越坚信这一点。
编写简单的代码
在代理环境中,简单代码的性能显著优于复杂代码。我最近写了一篇关于“丑陋代码”的文章,我认为在代理环境中,这篇文章值得重读。让代理做“最愚蠢但可行的事情”。
- 与类相比,优先使用具有清晰、描述性且比通常的函数名称更长的函数。
- 避免继承和过于聪明的黑客行为。
- 使用纯 SQL 。我是认真的。您可以从代理那里获得出色的 SQL,他们可以将自己编写的 SQL 与 SQL 日志进行匹配。这样可以避免代理最大限度地利用您的 ORM 功能,并迷失在日志中的 SQL 输出中。
- 将重要的检查保留在本地。你一定要确保权限检查对AI来说非常清晰,并且它们在AI可见的地方进行。将权限检查隐藏在另一个文件或某个配置文件中几乎可以保证AI在添加新路由时会忘记添加权限检查。
使其可并行化
单个代理的速度并不特别快,但并行化可以提升整体效率。找到一种管理共享状态(例如文件系统、数据库或 Redis 实例)的方法,以便可以运行多个代理。避免使用它们,或者找到一种快速拆分内容的方法。
您的初始共享状态只是文件系统,第二次签出即可。但实际上我还没有一个完美的解决方案。有一些不错的初步尝试。例如,值得关注的工具之一是container-use 。它是一个 MCP 服务器,可以指示 Claude 或其他代理完全在 Docker 中运行他们的实验。
然后,像 Cursor 的后台代理和 Codex 这样的工具正在将所有这些内容迁移到 CI 中,这将会很有趣。到目前为止,我还没有看到效果,但让我们一个月后再看看吧。
学习重构
代理编码会改变重构优先级。代理能够有效地处理任务,直到项目复杂度超过某个可控阈值。这里的“太大”是指它需要考虑的内容总量。例如,你可以先用 Vibe 代码整合前端一段时间,但最终你还是会遇到需要它创建一个组件库的情况。为什么?因为如果 Tailwind 类库的混乱被拆分成 50 个文件,你会发现很难让 AI 在不出现重大回归的情况下进行重新设计或提取组件。
代理式工作流程鼓励在适当的时机进行良好的代码维护和重构。你既不想做得太早,也绝对不想做得太晚。
下一步是什么?
代理编码正在快速发展,我今天的工作流程明天可能就会截然不同。但显而易见的是,将代理集成到你的开发流程中可以显著提升生产力。我鼓励你继续尝试。工具和技术会不断发展,但核心原则——简单性、稳定性、可观察性和智能并行化——将始终至关重要。
最终,我们的目标不仅仅是利用代理更快地编写代码,而是编写更好、更易维护、更灵活的代码。如今,这些代码已经不再像几个月前那样糟糕。保持适应能力,快乐编码!
[1] | 这不是 Claude Code 的广告。它只是我目前使用的代理。还有其他什么?用户体验类似的替代方案包括OpenCode 、 goose 、 Codex等等。此外,还有Devin和 Cursor 的后台代理,但它们的工作原理略有不同,因为它们在云端运行。 |