用 AI 编程工具的人多半见过这个场面:它一路说得很笃定------「已定位到问题」「已修改 shipping.ts」「测试已通过」------你一看文件,一个字没动。
第一反应通常是「这模型在胡说」。
但读完六套编程智能体的源码(Codex、Claude Agent SDK、Gemini CLI、DeepSeek Harness、pi、OpenCode,笔记整理成了一套开源中文教材,文末有链接)之后我发现,这个判断不太对。模型不是在撒谎,它根本不知道文件有没有被改。
因为改文件这件事,压根不是它干的。
1. 三个角色,别混成一个
读 Agent 源码最容易犯的错,就是把所有行为都算到模型头上。实际上一次任务至少牵扯三方:
Model 产生候选决定 ------ 只是「建议这么做」
↓
Harness 把候选接进任务循环 ------ 检查、放行、调用执行器
↓
Environment 副作用真正发生的地方 ------ 文件、进程、网络
模型输出的从来不是动作,是动作的请求。简化成教学示例大概是这样:
json
{
"type": "tool_call",
"name": "read_file",
"arguments": {"path": "src/shipping.ts"}
}
这一刻文件还没打开。模型不知道宿主进程有没有读权限,也不知道产品策略会不会放行这个路径。它只是提了个请求。
所以当它说「我读了文件」,准确的说法是「我请求读文件,并且假设那一步成功了」。
2. Harness 在中间做了六件事
夹在中间的这一层通常要负责:
- 把系统提示、用户消息、历史、工具定义、动态状态拼成模型输入
- 发起模型请求,消费流式或非流式响应
- 区分文本完成、工具请求、错误、取消、超时
- 检查工具名、参数、权限、用户确认
- 调执行器,把结果写回消息历史
- 保存 Session、发事件,决定继续还是结束
六套实现切法完全不同。Codex 把核心循环、协议和多种界面分到不同 crate;OpenCode 让多个客户端连到服务化的 Session;Claude 的 Agent SDK 只公开应用进程能看到的部分,产品内部怎么做我们看不到。职责看着相似,模块名字对不上。
回到那个运费 bug:Harness 拿到 read_file 请求后,先交给工具注册表,检查路径在不在工作区内,然后才真的去读。读到的内容回写进下一轮:
json
{
"type": "tool_result",
"tool_call_id": "call-1",
"content": "export function shippingFee(total) { return total > 100 ? 0 : 10 }"
}
模型看到这个结果,才知道边界条件确实写错了,才有依据提出把 > 改成 >=。
在这之前它说的任何「我看到了」,都只是它以为。
3. Environment 才是说了算的那层
策略放行了 src/shipping.ts,文件仍然可能打不开:文件不存在、宿主进程没权限、Sandbox 没挂载那个目录、远程工作区断线。
这些都是执行失败。把它们记成「策略拒绝」,账就算错了------你会以为是权限配置问题,实际是环境问题,排查方向全反。
反过来也一样:宿主进程也许有能力删掉整个仓库,但 Harness 不该因此就批准删除。系统能做什么,和产品授权它做什么,是两笔账。
4. 一张排查表
出问题时先判断落在哪一层,能省很多时间:
| 现象 | 更可能是哪一层 | 去查什么 |
|---|---|---|
| 模型压根没请求任何工具 | Model 或输入构造 | 模型请求、工具定义、上下文 |
| 工具请求被判为未知 | Harness | 工具注册和名称解析 |
| 请求被规则拒绝 | Harness | 权限策略、确认结果 |
| 请求获准但文件打不开 | Environment | 路径、挂载、系统权限 |
| 测试退出码 0 | Environment 事实 | 再查测试有没有覆盖用户目标 |
最后一行值得单说。
5. 「测试通过了」也不等于「做完了」
退出码 0 要成为有效证据,得同时满足三件事:
- 它覆盖了用户目标
- 跑的是修改后的代码
- 输出确实来自这次任务
三条缺一条,这个 0 就说明不了问题。举个具体的:如果测试只跑了 shippingFee(101),退出码是 0,但金额 100 的那个 bug 一点没修------而那正是用户要解决的问题。
进程状态和任务结果是两回事。前者是 Environment 的事实,后者需要按明确口径去判断。
6. 问题考察
下面四句话,分别是谁作出的判断?
- 「下一步应当把
>改成>=。」 - 「本次写入需要用户确认。」
- 「
shipping.test.ts返回退出码 0。」 - 「边界值测试通过,且测试文件未被修改,因此任务结果合格。」
依次是:Model 提出的候选做法、Harness 按策略作出的决定、Environment 里真实发生的执行结果、以及对整个结果的独立判定。
第三句推不出第四句------你还得确认那个退出码对应的是正确的命令、正确的工作区、以及这一次的补丁。
7. 所以回到开头那个问题
Agent 说「我已经改好文件了」,能不能信?
不能,但也不该怪它。 它说的是「我提了这个请求」,中间隔着两层它看不见的东西。想确认到底改没改,去看 Environment:文件内容、Diff、退出码。
这也是为什么读 Agent 源码时,第一件该做的事不是找「智能」在哪,而是先把这三层的边界画出来。边界画清楚了,你才知道一个 bug 该去哪一层排查。
8. 全量笔记
这篇讲的是三层边界,是理解 Agent Harness 的地基。完整教材还包括:配置和 Prompt 怎么拼成模型看到的输入、工具循环怎么转、权限在哪几层拦、Turn 和 Step 的边界在哪、会话状态怎么存和恢复、多个子 Agent 怎么协作------六套实现每套一条课程,外加横向对比。
-
Agent Harness 源码内核 plwslpld-arch.github.io/agent-harne...
-
Eval Harness 源码内核 (智能体跑完了,谁来判断它真做对了) plwslpld-arch.github.io/eval-harnes...
免费开源,觉得有用点个 star,写错的地方欢迎提 Issue。