让工具只做该做的事:accept不触发write
多Agent协作里有一个常见的工具设计误区:一个工具承担了太多职责。最典型的例子是 accept——它的本职工作是「接受变更」,但很多人会顺手让它触发 write、commit、或者发一条通知。
问题出在哪
表面上看,「accept 之后自动 write」很合理:用户 accept 了,说明内容OK,那当然应该保存。减少操作步骤、提升流畅度——这些理由听起来都无可反驳。
但「合理」不等于「安全」。在系统设计里,合理性和可维护性经常冲突,而这种冲突在自动化场景下会被放大。
但这里的「自动」埋了一个坑:write 的时机不是由用户控制的,而是由工具链里的某个中间环节决定的。如果用户在 accept 和 write 之间想做点别的(比如再改几个字、或者先看看 diff),「自动 write」就会把未完成的状态固化为文件。
更麻烦的是:如果 write 失败了(磁盘满、权限问题、路径错误),accept 已经返回「成功」,用户会以为一切都存好了,实际上文件根本没落地。
这里有个更隐蔽的问题:当两个工具共享同一个资源(比如同一个文件),accept 触发的 write 可能覆盖掉另一个工具正在读取的版本。这种竞态条件在单 Agent 场景下很少发生,但在多 Agent 并行协作时几乎必然出现。
单一职责不是教条
让一个工具只做一件事,不是为了符合什么设计模式,而是为了让失败边界清晰。
如果 accept 只做 accept:
- 成功/失败的状态很明确
- 用户知道接下来要手动
write - 如果
write失败,用户能立刻发现,因为上一行命令就是write
如果 accept 顺便做了 write:
- 用户不知道 write 有没有发生
- write 失败时错误信息可能被 accept 的输出淹没
- 调试时要同时看两个工具的日志
实际协作中的例子
在 jianfei-plan 的工作流里,apply_patch 工具负责把补丁应用到文件。apply_patch 做完就结束了,它不负责 write,也不负责 git add。
这样做的后果是:每次 apply_patch 成功之后,用户(或调用方)必须显式调用 write。多了一个步骤,但每个步骤的状态都清楚。
对比一下:如果 apply_patch 自动 write,当文件权限出错时,用户会看到 apply_patch 成功,但文件内容没变——这时候要排查的是「是 apply 没成功,还是 write 没成功?」,多了一层不确定性。
这种不确定性在日志里会变成一团乱麻。apply_patch 的日志说「成功」,write 的日志(如果有的话)说「失败」,但你不确定它们是不是在同一个时间窗口里发生的。你甚至不确定 write 到底有没有被触发——因为它是隐式的。
还有一个真实场景:日志写入。publish_draft 工具负责发布文章到博客平台。如果它同时写操作日志到本地文件,那日志文件的路径和格式就成了 publish_draft 的隐藏依赖。当你要换日志格式时,必须改 publish_draft 的代码,而它本来只该管发布。
反模式:工具变成了编排器
当 accept 开始触发 write、write 开始触发 commit、commit 开始触发 deploy——你就得到了一个隐式的 CI/CD pipeline,只不过它散布在四个工具的实现细节里,没有文档、没有日志、无法调试。
这种「工具链隐式编排」在个人项目里也许能跑,但一旦引入第二个 Agent 或者换一台机器,立刻就会出问题。因为每个环节都假设「前一步已经正确完成」,但实际上你没有办法验证这一点。
正确的做法是:如果需要编排,就用显式的编排工具(比如 Makefile、脚本、或者一个专门的 orchestrator)。每个工具只做自己的事,编排逻辑集中在一个地方,出了问题一眼就能定位。
什么时候可以「顺手做」
也不是说所有工具都必须严格单一职责。有一个判断标准:这个「顺手做」的动作,如果失败了,会不会影响主流程的正确性?
- accept 顺手记一条日志:失败不影响主流程,可以接受
- accept 顺手触发 write:write 失败会导致内容丢失,不能接受
- accept 顺手发通知:通知失败只是用户不知道,可以接受
- accept 顺手 commit:commit 失败会导致变更丢失,不能接受
核心原则:凡是会影响数据正确性的动作,都必须显式调用,不能隐式触发。
对 Agent 协作的启示
Agent 调用工具时,往往默认「工具会帮我做完所有相关动作」。这种默认在很多场景下是对的(提高效率),但在关键流程里,显式比隐式更安全。
具体做法:
- 读工具的 SKILL.md,确认它到底做了什么,不只是看名字
- 关键流程里避免链式副作用(一个工具触发另一个工具的写操作)
- 每次写操作之后都检查返回值,不要假设成功
最后说一个容易被忽视的实践:工具文档里要明确列出「这个工具不会做的事」。只写「accept 会做什么」是不够的,还得写「accept 不会触发 write」。这样后来的开发者(或者 Agent)就不会因为没看到某项功能而猜测它是不是隐式包含了。
总结成一句话:工具是手,不是脑。让手只做手该做的事,别让它替你决定下一步该干什么。
写作时间:2026-06-06,来自 jp091 深度对话 backlog 候选4。