跳到正文

让 Claude 做判断、pi 做执行:EvoClash 的双 agent 协作

约 7 分钟
目录

本文由 Claude Code 完成,下文中的我指代 Claude 模型。其实核心就是一点:和 Claude 说你来指挥 pi,要有一个游戏开发团队。 然后你来指定有那些团队完成项目(看下文)就行了 最终项目由 opus5.5 和 grok4.7 完成

背景#

EvoClash 是我在写的一个俯视角多人射击游戏:Rust + Bevy,客户端编成 wasm 跑在浏览器里,服务端权威、客户端预测,部署用 Helm。项目不大,但牵涉的领域很杂,基本是和 Claude Code 一起写出来的。

用久了发现一个问题:一个跨服务端、客户端和部署的功能做下来,Opus 的 token 大部分花在了读文件、跑 clippy、修编译错误、再跑一遍上。这些工作并不需要最强的模型。更麻烦的是,每次工具调用的输出都会留在主会话的上下文里,后面每一轮请求都要带着它们,会话越长,单次请求越贵。

真正需要强模型的地方其实很少:把需求想清楚、拆成边界清楚的任务、判断哪里容易出错、审查结果。

核心思路:判断和执行分开#

于是我把工作分成两层:

  • Claude Code(Opus)负责判断:理解需求,拆任务,为每个任务写一份 brief,审查结果,管理 git。
  • pi 负责执行:pi 是一个轻量的终端 coding agent,可以接任意模型。我给它配了一个便宜得多的模型,让它读代码、写代码、跑检查。

两层之间只通过两样东西交流:Claude 写给 pi 的 brief,和 pi 交回来的一份不超过 15 行的报告。pi 中间读了多少文件、试错了多少次,都留在 pi 自己的日志里,不会进入 Claude 的上下文。

省下来的 token 来自两处:执行部分按便宜模型计价;Claude 的上下文保持精简,后续每一轮都更便宜。

落地方式#

按文件划分角色#

项目里定义了五个角色:策划、服务端、客户端、美术、测试。划分依据是文件归属,而不是功能步骤。比如服务端只能改 crates/server、crates/game 和 deploy/,测试只能写 #[cfg(test)] 代码。

这样做的好处是,同时运行的几个 agent 天然不会改到同一个文件;协议定义、Cargo.toml 这类多方共用的文件,由 Claude 先改好,再分发任务。

角色定义放在 .pi/agents/ 下,每个角色还可以单独设置推理强度:测试用 low,涉及确定性模拟的服务端和客户端用 high。

把 pi 包装成一个命令#

pi 默认是交互式的。为了让 Claude 能在后台派发任务,我写了一个几十行的脚本 scripts/pi-run:

Terminal window
scripts/pi-run [-C worktree] [-m model] <role> <brief.md> [session]

它读取角色定义作为 system prompt,用非交互模式运行 pi,把完整的事件流写进日志,最后只把 pi 的最终回答作为报告打印出来。对 Claude 来说,这就是一个”输入 brief、输出报告”的函数。

围绕它有两个约定,让协作更顺畅:

  • pi 中途无法提问,所以遇到需要决策的地方就停下,在报告第一行写 需要决定:<问题>。Claude 回答后,用同一个 session 名再运行一次。
  • 追问和补充任务都发回原来的 session。原会话已经读过相关代码,接着做比新开一个便宜得多。

写进 CLAUDE.md 的规则#

脚本只解决了”能派”,什么时候该派、怎么派,写在 CLAUDE.md 里。最重要的几条:

  • 小事不派。单文件修改、简单问题由 Claude 直接处理,派发本身也有成本。
  • brief 要短且自包含:目标、可以改哪些文件、验收标准。
  • 报告要短:约 15 行,给出 file:line,不贴代码。
  • 检查范围要小:只对改动的 crate 跑 clippy 和测试,完整 CI 只在提 PR 前跑一次。

质量从哪里来#

便宜模型的主要风险是”看起来完成了,其实没做对”。我靠四件事来控制:

  1. 验收标准写在 brief 里。“helm lint --strict 两种配置都要通过""action 的版本 SHA 从现有 workflow 复制,不许自己编”,这类要求让 pi 能自己验证结果。
  2. 高风险改动不交给便宜模型。客户端预测、延迟补偿、需要跨平台逐位一致的地图生成,这些代码出错不会编译失败,只会在线上表现为卡顿或不同步。遇到这类任务,要么给 pi 换更强的模型,要么由 Claude 先写好接口和设计。同一个错误失败两次就直接换模型,不再重试。
  3. Claude 审查时对照真实代码,而不是只看报告。
  4. CI 把关。main 有分支保护,必须通过 fmt、两遍 clippy、测试、依赖审计,以及构建镜像并真正跑起一局游戏,才能合并。

第三点最容易被低估,下面这个例子就是它起作用的时候。

一个例子:单节点 k3s 部署#

最近一次比较完整的协作,是把 EvoClash 部署到一台 1 核 2G 的小服务器上。Claude 先花了一些时间了解现状,然后写了两份 brief,交给两个 pi 会话并行处理:一个通过 SSH 安装精简版 k3s,一个给 Helm chart 加单节点模式并编写 CD 流程。

两个 pi 在后台工作时,Claude 在前台和我一起配置 Cloudflare。这部分需要我做决定、提供权限,本来就适合留在主会话。中途我想加一个卸载 k3s 的脚本,Claude 把这个需求发给了负责安装的那个 pi 会话:它清楚自己装了什么,写出来的卸载步骤很完整。

pi 交回的报告显示 lint、clippy、测试全部通过。但 Claude 审查时去核对了 nginx 放行的路径和客户端实际请求的路径,发现客户端首页用到的 /v1/rooms 不在白名单里。这个遗漏其实来自 Claude 自己写的 brief,原有配置里也漏了它,所有检查都发现不了,上线后首页会直接是空的。

这就是分层的意义:pi 忠实地执行了 brief,而审查环节回过头去对照代码,补上了 brief 本身的漏洞。

这次任务两边的用量如下:

Claude Code(Opus)pi(两个会话合计)
API 请求10380
工具调用77120
输出 token5.6 万13.6 万
缓存读取 token1431 万453 万

约 70% 的输出 token 由 pi 产生。Claude 的 77 次工具调用中,有不少是配置 Cloudflare 和 GitHub、与我确认细节,真正用于派发和审查的只是其中一小部分。pi 的 120 次工具调用如果放在 Opus 的会话里,除了按 Opus 计价之外,它们的输出还会一直留在上下文中,让后续每一轮都更贵。

局限#

这套方式的上限取决于 brief 的质量。便宜模型很少质疑需求,brief 漏了什么,结果就会缺什么。所以能省的是执行,省不掉的是想清楚问题和认真审查。

另外,pi 接入的模型服务偶尔会连接失败,目前需要 Claude 发现后手动重跑,还没有做自动重试。

总结#

整件事的核心只有一句话:贵的模型用来判断,便宜的模型用来执行,中间用 brief 和简短的报告隔开上下文。

具体到实践,就是:

  • 把另一个 agent 包装成”brief 进、报告出”的命令,让执行过程不污染主会话;
  • 按文件归属划分角色,避免并行冲突;
  • 在验收标准、模型升级、审查和 CI 这几处守住质量。

不一定要用 pi,任何能非交互运行、能接其他模型的 coding agent 都可以套用这个模式。最值得先做的,是在 CLAUDE.md 里把”谁做判断、谁做执行”写清楚。