从零构建 Coding Agent
English

10. Coding 工具:read、edit、write、bash

Coding Agent 和普通工具 Agent 的差异,集中体现在写操作。读文件和搜索让模型观察世界;editwritebash 让模型改变世界并验证结果。这里的风险也最高:误改文件、覆盖用户改动、执行危险命令、输出过长、测试进程挂住,都会在真实仓库里发生。

先读再改

系统提示词和工具运行时都应该强化一个规则:编辑前必须读取目标文件。模型如果直接调用 edit 修改未读文件,运行时可以拒绝并返回错误 tool result:

Cannot edit src/config.ts because it has not been read in this session. Read the file first, then retry with exact oldText.

这个错误不是保守主义。edit 的可靠性依赖精确上下文。模型没读文件时,通常会猜测代码形状,导致替换失败或误改。

Edit 不是“让模型给 patch”

教学项目推荐先实现精确替换型 edit:

type EditInput = {
  path: string;
  oldText: string;
  newText: string;
};

运行时读取当前文件,检查 oldText 是否出现且只出现一次,然后替换为 newText。如果没有出现,返回可修正错误;如果出现多次,也返回错误并要求模型提供更多上下文。这样做比让模型直接输出 patch 更容易校验,也更容易让模型自我修正。

失败反馈要具体:

oldText was not found in src/parser.ts.
The file currently contains a similar line:
  return parse(input, options);
Read the latest file contents and retry with exact oldText.

模型通常能根据这类反馈重新 read,然后发出正确 edit。

Write 的边界

write 用于创建新文件或整体替换文件。它比 edit 危险,因为它不要求 oldText 匹配。建议策略:

  • 创建新文件可以直接允许,但路径仍要在工作区内。
  • 覆盖已有文件前要求文件已读,或要求用户确认。
  • 对大文件写入给 UI 显示 diff。
  • 写入后把修改摘要放入 tool result。

不要用 write 修小改动。小改动用 edit,降低覆盖风险。

Bash 的边界

bash 是最强也最危险的工具。最小实现也要有:

  • cwd 限定。
  • 超时。
  • stdout/stderr 截断。
  • exit code。
  • AbortSignal 取消。
  • 危险命令确认或拒绝。

命令输出给模型时要保留命令、退出码、截断说明和关键输出:

Command: npm run check
Exit code: 1
Output was truncated to the last 200 lines.

src/index.ts:42:10 - error TS2322 ...

如果输出很长,保留尾部通常比保留中间有用,因为测试和编译错误多在尾部总结。

文件写队列

模型可能在一个 turn 中并行调用两个写工具。即使你的 loop 串行执行,也要为未来并行留下边界。对同一个真实路径,写操作必须进入同一队列:

edit src/a.ts -> waits for previous write to src/a.ts
write src/a.ts -> same queue
edit src/b.ts -> can run independently

队列 key 应该是解析后的真实路径。符号链接、相对路径和大小写差异都可能指向同一个文件。没有写队列时,两个工具会基于旧内容计算修改,最后一个落盘的覆盖前一个。

Diff 给人,摘要给模型

模型不需要完整 unified diff 才能继续,用户界面需要。edit 结果可以分两层:

  • 给模型:修改成功,文件、行数、下一步建议。
  • 给 UI:结构化 diff、旧内容、新内容、是否创建文件。

如果把完整 diff 都塞进上下文,Agent 很快会耗尽 token。模型真正需要的是“修改已完成”和“测试下一步应该跑什么”。

练习

实现 editwritebash

验收标准:

  • edit 要求 oldText 精确出现一次。
  • edit 失败返回可修正 tool result,不抛出未捕获异常。
  • write 覆盖已有文件时有明确策略。
  • bash 有超时、截断、exit code 和取消。
  • 同一文件的写操作不会并行覆盖。
  • UI 事件能拿到 diff details,模型上下文只拿到短摘要。