派大猩 · Agent 工程笔记

Back

讨论 Agent 时,我很容易从模型开始:推理能力够不够,代码能力强不强,上下文能不能再长一些。这些问题当然重要。但阅读了一组工程实践之后,我开始用另一个问题检查自己的判断:任务没有完成,究竟是模型不会做,还是系统没有让它看见事实、执行动作和验证结果?

这两种问题的解决方式不同。前者可能需要更强的模型,后者往往需要重建工具、状态和反馈。把它们混在一起,我就可能不断更换模型,却没有碰到真正的故障点。

本文不是一份生产系统复盘,而是基于阅读形成的工程判断。我想讨论的也不是“模型已经足够强”,而是:模型能力不能单独解释一个 Agent 系统的可靠性。

一、写出了代码,不等于完成了任务#

先看一个假设场景。用户让 Coding Agent 修复重复提交订单的问题。它读了接口,增加一个去重判断,运行了测试,然后宣布修复完成。整个过程看起来很顺利。

但这里至少有三个未回答的问题:测试是否真的覆盖了并发提交?去重状态能否跨实例共享?当请求超时、客户端重试时,系统会返回原结果,还是执行第二次写入?

如果工具只告诉模型“测试通过”,没有展示测试范围和实际执行记录,那么它获得的反馈是不完整的。即使代码写得合理,也不能从这条反馈推出业务问题已经解决。这里缺少的不是一句“请仔细检查”,而是验证路径。

吴佐衍在《从 Spec 驱动转向环境与验证驱动——我对 AI Coding 的一点思考》中,把关注点从代码生成转向构建、部署、测试、数据、日志和发布链路。(原文 ↗)我不把文中的模型成绩或团队效率数字当成通用结论;更值得保留的是它提出的问题:生成之后,Agent 能不能继续获得足够可靠的反馈?

代码是交付物的一部分。需求边界、运行条件、验收证据和发布责任也是交付的一部分。只优化其中一个环节,不能自动消除其他环节的等待与风险。

二、Harness 的作用,是让行动与事实接上#

我把 Harness 理解为 Agent 的运行外壳。它负责组织上下文、调用工具、保存状态、控制权限、记录过程,并决定什么时候继续、停止或请求人工帮助。

这个定义比“模型外面套一个循环”更宽。一个循环可以让模型不断尝试,却不能保证它有权执行动作,也不能保证失败被正确记录。没有这些条件,更多轮次可能只是重复相同的错误。

为了避免把 Harness 变成另一个含糊的大词,我更愿意把它拆成六个问题:

  • 看什么:代码、文档、日志和历史经验如何进入上下文?
  • 能做什么:工具开放哪些动作,哪些动作需要批准?
  • 怎样推进:任务如何分步,依赖如何检查?
  • 记住什么:当前状态与跨任务经验如何分开保存?
  • 怎样验收:结果由什么证据支持?
  • 出错怎么办:如何重试、恢复、回滚和交接?

这六项不是要求每个项目建设六个平台。一个小项目可以用几条脚本、明确的工具返回值和简单状态记录回答它们。重要的是责任存在,而不是组件数量。

例如,Agent 运行测试时,工具至少应返回执行命令、退出码和关键输出。无法运行应当明确标记为无法验证,不能与测试失败混为一谈,更不能被模型解释成已经通过。

三、Prompt、Skill 和执行约束,不是一种东西#

在学习 Skill 时,我最想保留的区别是:说明怎样做,与强制只能这样做,属于不同层次。

Prompt 适合表达目标、背景和高频提醒。Skill 适合封装特定任务的方法、资源和步骤。Harness 则需要承接工具执行、权限检查、状态更新和失败处理。三者可以合作,但不能互相冒充。(原文 ↗)

假设我在 Skill 中写下“禁止修改生产数据”。这是一条有价值的指令,却不是可靠的访问控制。如果工具仍然持有生产写权限,风险并没有因这句话而消失。模型可能误解环境,也可能被不可信内容干扰。

更稳妥的方式是:默认提供只读工具;写入动作由独立接口承接;服务端检查身份、目标环境和参数范围;高影响动作在执行前进入确认流程。自然语言说明负责帮助模型理解,执行层负责拒绝越界请求。

同样,“不得重复创建订单”不能只写在提示词里。是否重复、是否允许写入,需要由业务系统根据当前状态检查。模型可以提出动作,但不能靠自己的文字承诺消除并发与重试。

这并不是否定 Prompt 或 Skill。恰恰相反,明确各自边界之后,我才能把模型的注意力用于语义理解,而不是要求它承担所有系统不变量。

四、工具返回成功,还需要说明成功到了哪一步#

很多 Agent 的失败不是完全没有反馈,而是反馈粒度太粗。一个“success”可能只表示请求已接收,也可能表示任务已经完成;如果接口不区分,模型就容易跳过后续检查。

假设某个工具返回“部署成功”。它究竟是上传了构建产物,创建了发布任务,还是新版本已经接到流量并通过健康检查?这三个状态对下一步行动的要求完全不同。

我认为,好的工具接口应尽量返回可观察状态,而不是宽泛评价。比如任务已创建时给出任务标识;执行完成时给出结果;执行失败时给出错误类型和可恢复信息。模型可以解释这些事实,但不应凭语气补齐缺失状态。

反馈还需要有适当的结构。完整日志可以保留为证据,但直接把大量无关输出塞给模型,也可能掩盖关键错误。更合理的做法是先返回状态、摘要和证据入口,再按需读取细节。

这就是我理解的上下文工程:不是尽可能提供更多文本,而是让当前决策拿到需要的事实,并保留回到原始证据的路径。

五、长任务需要状态,而不是一段越来越长的聊天#

短任务的状态可能完全放在上下文中。但任务跨越多轮工具调用、人工审批或会话中断后,仅靠聊天就不够稳妥。

仍然用订单修复的假设场景。Agent 已经完成修改,却在测试阶段中断。下一次恢复时,系统至少要知道:代码改到了哪个版本,哪些检查已执行,哪些结果仍然有效,还有哪些动作等待确认。

一段“之前已基本完成”的摘要不够。它没有定义“基本”包括什么,也没有说明证据对应哪个版本。如果恢复后代码已经变化,旧测试结果就不能直接充当新版本的验收证据。

因此,我会把任务状态与模型叙述分开。状态保存阶段、工件版本、验证记录和待处理事项;叙述解释原因和下一步建议。前者帮助系统恢复,后者帮助人和模型理解。

这也影响重试设计。读取失败通常可以重新读取;写入超时却可能意味着动作已经发生,只是响应没有返回。系统应先查询实际结果,或依靠业务接口的幂等机制处理,而不是让模型猜测“再试一次应该没事”。

六、验证路径需要与风险匹配#

强调验证,不意味着所有任务都要走同一条重型流程。改一句文档与调整支付逻辑的风险不同,验收成本也应不同。

对低风险文本修改,可以检查内容、链接和格式;对代码修改,需要构建、测试与差异审查;对具有外部副作用的操作,还需要权限、当前状态、执行结果和恢复方案。验证应覆盖任务的主要失败方式,而不是机械增加检查数量。

还有一个容易忽略的问题:执行验证本身也需要环境。测试依赖缺失、服务不可用、数据无法构造,都会阻止闭环。此时应记录阻塞原因,而不是把“没有发现问题”包装成“已确认正确”。

模型可以帮助设计测试和分析错误,但测试通过也只是某个范围内的证据。需求遗漏、测试盲区和生产环境差异,仍然需要其他检查补足。

七、我会先补哪一块,而不是先买哪一块#

如果要改进一个不稳定的 Agent,我不会从“再加一个规划 Agent”开始,而会先检查最近的失败轨迹。

我会把问题分成四类:目标理解错误,必要信息缺失,动作执行失败,验收判断错误。分类不必完美,但需要指向不同改动。

理解错误时,补需求与约束;信息缺失时,补检索或读取入口;执行失败时,补工具契约与恢复机制;验收错误时,补测试与证据检查。只有确认模型在信息充分、工具可用的条件下仍然做不好,升级模型才是更直接的候选方案。

改动之后也要对照。保持任务集和验收条件可比,记录成功率、耗时、成本和人工介入。一次演示成功不能证明改动有效,更不能证明所有失败都已经解决。

这套顺序的价值,在于避免把系统问题误诊为模型问题,也避免把模型问题硬塞进复杂流程里。

八、可靠性不是把 Agent 管得越来越死#

Harness 也有成本。每增加一个步骤,都可能增加延迟、维护工作和新的失败点。把开放式研究强行拆成固定节点,可能限制探索;把简单任务塞进多层审批,也可能让用户失去使用意愿。

因此,我倾向于区分两类内容:需要探索的路径,与不能违反的条件。模型可以自主决定如何查找资料、形成候选方案;授权范围、数据边界、验收要求和发布责任则应明确。

不是所有动作都固定,而是关键风险可控;不是所有判断都交给代码,而是能够确定计算的部分不再依赖模型猜测。

阅读这些资料后,我对 Agent 的期待更具体了:它不仅要提出一个看起来合理的答案,还要能说明依据,留下过程,并在无法证明时承认边界。

模型决定系统能尝试哪些任务;运行环境、反馈和控制决定这些尝试能否变成可信交付。 这不是二选一。只是在讨论可靠性时,我不再愿意把所有问题都归到模型能力上。