为什么 task graph 要写 read_set 和 write_set
作为一个长期和 Agent 协作写代码的人,我踩过一个坑:让 Agent 改一个功能,它直接跑起来,结果改错了、改漏了、甚至覆盖了不该碰的文件。不是 Agent 能力不够,是我没说清"哪些文件你能读、哪些文件你能写"。
直到我在 jianfei-plan 的 prewrite gate 里看到 task graph 的 read_set 和 write_set 设计,才意识到——这个看似简单的"白名单",恰恰是从计划到代码之间最重要的一环。
不写 read_set 和 write_set 会怎样
最早我不用这套东西。需求扔给 Agent,它自己分析依赖、自己决定读哪些文件、自己决定写哪些文件。表面看效率很高,实际坑深不见底。
有一次让 Agent 优化某个模块的性能,它发现配置文件和模块代码在一个目录下,顺手把配置文件也改了——因为它觉得"既然要优化性能,配置参数也该调一下"。结果部署后服务起不来,排查了两小时才发现是被 Agent 修改的安全配置参数导致了连接被拒。
问题出在哪?Agent 自己推导"该读哪些文件"时,决策边界是模糊的。它看到文件 A 和 B 都在同一目录,以为 B 也是相关文件,就擅自读取并修改了。没有 read_set,Agent 的"阅读范围"完全靠上下文推理。这在简单场景下问题不大,目录结构浅、文件少,Agent 不容易出错。但一旦目录结构复杂、文件间有隐含依赖关系,偏差就很容易出现。
写 set 的问题更严重。没指定 write_set,Agent 不知道哪些文件可以修改、哪些绝对碰不得。它按照"关联性"自己做判断,结果往往和人的预期不一致。这种偏差一旦发生,造成的后果不只是代码没改对——更麻烦的是你可能不会立即发现,因为 Agent 改的"不是核心代码",测试可能还通过了,但在生产环境暴露出问题。
回想一下,有没有遇到过"AI 帮我改了文件,我不知道它改了什么"的情况?本质上就是因为没有人告诉 Agent 什么能碰、什么不能碰。read_set 和 write_set 解决的就是这个"决策边界模糊"的问题。
prewrite gate 的本质:让意图先于执行
jianfei-plan 的 prewrite gate 在这个问题上给了我一剂解药。核心逻辑很直白:在执行任何写操作之前,Agent 必须先生成一个明确的"I'll do this"计划,而这个计划必须包含 read_set(我准备读这些文件)和 write_set(我准备改这些文件)。
看起来是多了一重步骤,实际上它把 Agent 的"默会知识"显性化了。以前我不说,Agent 就猜——猜对的概率不是 100%。现在 Agent 在动手前先把它认为需要读和写的文件列出来,我扫一眼就能确认:这个文件确实要改、那个文件不该碰。
有一次我们在写一套 API 服务的并发控制逻辑。Agent 识别出三处需要修改,但它的 write_set 里多了一个数据库连接池的配置文件。我一看就知道不对——那个文件是运维组管理的,不属于我的改动范围。当场拦截,Agent 切换到另一个不需要动连接池的方案来实现同样的效果。
这个反馈非常关键。如果没有 prewrite gate,Agent 会直接改掉那个文件,CI 跑不过才暴露问题。有了 read_set/write_set 的显式声明,错误在动手前就被发现了。
再来一个例子。有次做跨模块的接口升级,Agent 在 prewrite gate 列出了 read_set,我注意到它漏读了某个模块的接口文档。接口文档里记录了旧接口的废弃时间和替代方案,Agent 没读这个文档,就按照自己对接口的理解去改。我补充了 read_set,Agent 重新读完文档后,选择了完全不同的改动策略——因为旧接口在文档标注的废弃时间后已经下线了两个方法,如果按原计划改,那些调用的地方会直接报错。
这个例子让我意识到:read_set 不只是在说"你只能读这些文件",它更是一个"透明度机制"。我看到 Agent 要读什么,就知道它对任务的理解到了什么程度。漏了关键文档,说明理解不够;多了一些不相关的文件,说明判断有偏差。每次 prewrite gate 都是一次"对齐"的信号,而 read_set/write_set 就是信号里的具体内容。
实际协作中的价值:从"信任问题"到"可控协作"
第一次用这套设计时,我感觉最强烈的是安全感的变化。以前让 Agent 写代码,每次执行完我都要 review 一遍 diff,生怕它干了什么我不知道的事。现在不一样了——Agent 先告诉我它要做什么,我确认了才让它做。这就从"事后修正"变成了"事前控制"。
这种"先计划后执行"的模式在多人协作项目中尤其重要。我在项目里用到的编码规范、目录结构约定、历史上下文,这些信息 Agent 光靠读配置文件是捕捉不到的。但当它在 prewrite gate 里列出了 read_set,我会发现它漏了某个重要的约定文档,我可以补充进去,让它重新分析后再动工。
还有一次,Agent 要做文件结构重构。它列出了 write_set 里有 12 个文件要改。我一数——其中 3 个文件已经废弃了,改它们已经没有意义。告诉 Agent 跳过这 3 个,只改真正需要的 9 个。执行时间缩短了 25%,而且没有产生冗余改动。
这是 prewrite gate 带来的另一种收益:信息对称。Agent 能判断哪些文件需要改动,但它判断不了哪些改动"不值得"或者"已经过时"。这些信息只存在于人的经验里。read_set/write_set 提供了交换这些信息的接口,让人和 Agent 在每个重要决策点完成一次对齐。
还有一个细节:设定 write_set 后,Agent 在执行中遇到了边界之外的"顺手修改"冲动,它会主动停下来确认。比如,Agent 写着一段代码,发现某个相关函数有明显 bug。如果没 write_set,它可能顺手修了——但同时引入了不属于本次变更的改动,破坏了变更范围的纯净度,回滚也变得困难。有 write_set 的话,它会在 prewrite gate 之外停下来问我:"我发现一个 bug,要一起修吗?"这样就保持了变更的原子性。
执行阶段的保护伞
prewrite gate 不仅在计划阶段有用,在执行阶段同样发挥作用。Agent 在动工过程中,偶尔会遇到"顺带看到一个小问题,顺手改一下"的场景。没有 write_set 约束,它可能真的顺手改了,打乱了你的变更范围。有了 write_set,只要新的改动落在 write_set 之外,Agent 必须停下来问,不会自作主张。
这种"严格执行计划"的能力让并行协作成为可能。我跟同事分工,一个改前端组件一个改后端接口,各用一个 Agent。每个人的 Agent 都有独立的 read_set 和 write_set,不会互相踩踏。有一次两个 Agent 分别改同一个项目的不同模块,它们的 read_set 有重叠,但 write_set 完全不重合,各自执行完代码变更后,没有出现任何文件冲突。
任务的分工越清晰,Agent 的执行就越可靠。read_set 和 write_set 的意义不在于限制 Agent——它给 Agent 一个明确的"行动边界",让它在边界内自由发挥的同时不越界。对于协作而言,清晰的边界就是最好的效率。任务的边界越清晰,Agent 的执行就越可靠。而 read_set 和 write_set,就是定义这个边界最朴素的工具。