9月26日,五个构建代理同时开工,依据的是一份 markdown 契约——契约里写死了每个代理能改哪些文件。第一个本地版本 P0 跑起来时,服务挂在 127.0.0.1,224 个测试通过,没有任何请求离开这台机器。在这之前,一轮评审以 25 处修复收尾。比起测试数量,这个数字更能说明并行代理协作的真实状态。

打开网易新闻 查看精彩图片

这是 Refmade 系列的一部分。Refmade 是一个桌面应用,把 Claude Code 和 Codex 的会话画成一间办公室:每个会话是一支团队,主代理当组长,每个子代理当队员。Refmade 是独立应用,与 Anthropic、OpenAI 没有隶属关系。大部分代码由 Claude Code 和 Codex 代理在作者指挥下写成,作者负责规划、拍板和验收。界面目前是韩语,后续会改。

前几篇讲的是办公室为什么读本地日志、而不信任代理自己上报。这一篇讲第一个真正跑起来的版本,也就是 P0。

契约就是一张文件归属表

构建契约的作用,是固定并行工人之间的接口,并规定哪个工人可以编辑哪个文件。没有它,五个代理写同一个应用,会产出五套略有差异的“团队”对象定义。

契约放在仓库里,路径是 office-v2/P0-CONTRACT.md,文件日期为9月25日,如今约 40 KB,因为构建过程中不断追加章节,其中一节关于工作流视图,后面会提到。最关键的部分是一张归属表:每个构建者都在自己代码旁边写自己的测试;已有的 125 个测试被冻结;不新增依赖,要求 Node 18 或更高,只用 ES 模块;整个应用对 ~/.claude 和 ~/.codex 下的日志只读。

作者最在意的一条规则最短:“别人的文件是只读的。”要改接口,构建者必须先改契约并通知集成者,而不是去补别人的文件。人类团队用的也是这条规则,写给代理时同样需要落成文字。

回环端口对同机所有程序可见

服务器只绑定 127.0.0.1,这是简单的一半。不那么显然的一半是:回环端口对同一台电脑上的其他程序和用户账户都是开放的,所以“本地”不等于“私有”。

构建者 D 的契约章节用一个每次运行生成的密钥来回答这个问题。开发者命令 refmade open 会生成随机密钥,并打开 http://127.0.0.1:端口/#k=密钥 页面。密钥放在 # 之后,浏览器不会把它发到网络上。页面读取它,并附加到每个 API 调用上。校验逻辑在 cli/lib/office/server.mjs 里,用 timingSafeEqual 比对长度与内容。

?k= 这个回退入口存在,是因为实时流背后的浏览器 API EventSource 无法设置自定义请求头。Host 头不是服务器自身地址的请求会拿到 421,用来阻断 DNS 重绑定。静态文件不需要密钥,API 没有密钥则不返回任何内容。这保护的是办公室视图不被本机其他程序窥看,并不保护代理自身的工作,作者也没有这样声称。

评审轮发现了什么

五个构建者完工、集成者合并之后,作者从四个不同角度跑了一轮评审,再加一轮专门反驳每条发现。扛过反驳的发现才变成修复,总数是 25。

作者只保留了 25 这个计数,没有逐条清单。当天读日志踩到的坑,比如分叉的复制记录让每个队员显示两次、一次性运行看起来像一支休息中的团队,属于前一篇的主题。仍留在已知限制清单上的一条是:已完成请求下方那行“结果”,取的是代理最后一条回答的第一句,代理有时会在那里写很短的内部代号,然后显示到屏幕上。

出问题的两处不在代码里。工作流自带的评审与修复步骤因为撞上每周用量上限而失败,那部分由作者手工检查。执行器和文件监听测试套件里有几个计时测试在负载下会抖,单独跑则通过,它们也在同一张已知限制清单上。

要一个游戏,拿到三个

作者想让办公室更像游戏,同时要求去掉“轮到我了”这个说法——它用来表示请求在等用户,读起来别扭。这成了同一份数据契约上三套皮肤的简报,只允许改屏幕代码。

简报也列了不许出现的东西:粒子雨、彩虹、升级、奖励、炫耀 token、任何与真实事件无关的装饰。只有事情真的发生时才允许特效,比如请求结束时组长头上飘一个“+1 完成”。字体是 Galmuri,一款 OFL 许可的像素字体,随应用打包,不从 CDN 加载。

作者选了 B,管理模拟风。皮肤落地后测试数从 224 变成 245。

用大白话写状态

皮肤简报把状态词汇固定为五种:工作中、等待指令、需要你回答、未开始、运行结束。这些词后来漂移过。今天的游戏主题在计数器里写“工作中的人”“已完成请求”“等待确认”,上面再放更平实的句子。

应用里的一句状态是:“2 支团队在工作。1 支需要我回答。”有一条提醒从这一版延续下来:一张已完成请求卡片只代表请求结束了,不代表工作成功了。应用从日志里读回合边界,一个收尾很糟的回合同样算结束。

当晚还有一个词被删掉。作者告诉代理工作流运行在屏幕上显示不正常,代理理解成结构和角色不可见,于是加了一个流水线视图(横向是阶段,纵向是分支)和九个角色徽章,作为新章节写进契约。大约 22 点,作者说他们给分支选的那个韩语词读起来很怪,代理把它从代码和页面里删掉了。视图留下,词必须走。屏幕上的术语是产品的一部分,而代理会很乐意交付一个只在契约内部讲得通的词。

结果与它的边界

P0 完成时:224 个测试通过(选完皮肤后 245),首次加载约 2 秒,浏览器检查中没有对 127.0.0.1 之外的任何请求。数字来自作者当天的测试与渲染记录。

它们不能证明任何人都能用这个东西。记录里没有面向非开发者的可用性测试,245 个绿灯测试对此什么也说明不了。

接下来是一个好友功能,作者当天推上生产,又在同一天决定砍掉。应用链接会放在系列最后一篇里。

如果你把一个代码库拆给多个并行代理,共享文件归谁?你会像作者这样设一个集成者,还是让代理互相合并对方的工作?