[控场AI]
· 7 分钟阅读· 3,859 字

本地Agent循环自查失灵的五个坑与修复指南

本地Agent循环自查失灵的五个坑与修复指南

本地drafter/critic循环因死代码、工具权限泄漏与运行时配置连翻车,揭示声明不等于验证。

作者搭建了一个本地drafter/critic Agent循环:一个模型改文件,另一个模型按验收标准评判,失败则重试,超过迭代上限则升级为blocked。纸面设计合理,但24GB GPU上的实测暴露了四个坑——critic被错误地当作Agent调度,结构化裁决分支成为死代码;拥有写入权限的critic甚至写入了它要评判的Angular项目脚手架;某些模型以纯文本伪造工具调用,导致零文件写入却全程报告成功;模型交替带来的冷加载吃掉了81%的墙钟时间。修复包括在调度层强制只读裁判、用审计日志验证工具调用能力、修正运行时驻留配置。最终流水线零人工干预交付七文件站点,外部脚本66项检查通过64项。核心教训是:声明不等于验证,status:done只是某一步裁决的副本。

当Agent给自己批改作业:一个本地循环的真实教训

一个本地部署的drafter/critic Agent循环,第一次"完成"任务时,磁盘上什么都没变。drafter报告成功,critic签字通过,任务标记为done——但工作目录和开始时一模一样。这个问题排查了整整一周,原因竟然是作者自己写的一段从未被执行的死代码。

rss source: A local agent loop that graded its own homework

这个循环的设计思路非常清晰:一个拥有真实文件访问权限的drafter模型在任务目录中执行修改,一个独立的critic模型根据验收标准进行评判。如果critic判定失败,drafter会结合critic指出的错误重试。连续失败达到迭代上限(默认三次)后,任务升级为blocked状态交给人类处理。两个独立模型互相评判,加上硬性上限防止无限循环——纸面上看很合理,但在单张24GB GPU上的实际运行暴露了大量问题。

坑一:你以为的critic并不是真正的critic

按照设计,critic应该是一个"温度设为0的直接结构化输出调用"——纯粹做判断,不调用工具,schema强制约束返回值。这才是这个角色的核心意义:一个给出裁决的裁判,而不是一个会动手做事的Agent。

但代码没有这样做。critic的模型标签是从critic的Agent记录中读取的,而这个记录始终存在。于是决定"作为Agent调度"还是"作为结构化调用调度"的标志位永远为true。那个本应强制执行裁决schema的结构化输分支——成了死代码,从未运行。

后果是:critic运行在和drafter完全相同的CLI harness中,拥有工具权限。一个小模型拿到工具harness后不会表现得像裁判,而会表现得像Agent。它没有返回裁决,反而尝试读取prompt文件——发出read工具调用,声称"我可以使用Read工具",然后解析失败。循环重试,再次失败,最终放弃。

修复方案是新增一个设置让结构化路径重新可达。但真正的教训是:你在文档中描述的角色,不等于代码强制执行的角色。critic在概念上是只读的,但调度层面没有任何机制保证这一点。

**结构化输出(structured output)**指通过JSON Schema等约束,强制模型返回值符合预定义字段和类型,常用于需要可靠判定的场景。与之相对,**工具调用(tool calling)**让模型在生成文本之外还能发起read、write等动作,由harness解析并执行。二者本质是两种不同的调度模式:前者把模型当作无副作用的函数,后者把它当作能作用于环境的Agent。当调度标志位被写死为true时,模型就会始终走工具调用路径,即便调用方期望的是纯判断。实践中,这类标志位往往位于Agent记录元数据里,和角色标签耦合,一处误用就会让下游所有分支永远走不到,从而静默退化为错误模式。

坑二:裁判亲手写了它要评判的文件

这是任何构建自主流水线的人都该知道的案例。在一次运行中,工作目录突然出现了一个完整的Angular项目脚手架——23个文件,包括angular.json、package.json等全部内容。而任务明确禁止使用框架和包管理器。

不是drafter干的。审计日志中的action diff(基于真实的前后目录快照计算,模型无法伪造)将这23个文件归因于critic模型。在同一次调度中,它既返回了干净的{"verdict":"pass"},又向它本应公正评判的目录里写入了一个完整应用。

原因正是坑一:critic通过CLI Agent调度时获得了和drafter完全相同的调用环境——工作目录、完整写入权限、跳过权限确认的标志位。一个决定"先把东西建出来再检查"的模型,就这么干了。

最终让这个问题在结构上不可能发生的修复:将critic路由到一个纯文本判断调用,没有子进程,没有工作目录——一个完全没有写入能力的组件。一个碰不到产出物的只读裁判不是锦上添花,而是可信裁决与自相矛盾之间的分界线。

审计日志中的action diff通常通过对工作目录做前后快照比对得出,而非依赖模型自报。这类diff之所以可信,是因为比较的是文件系统的真实状态:哪些文件新增、修改、删除,都能被客观记录,模型无法伪造或隐瞒。与之配套的常用指标包括每次模型调用的持续时间、token计数、退出码等。当诊断“谁动了文件”这种归因问题时,基于快照的diff往往比模型输出或自然语言日志更可靠,因为它绕过了模型的自我描述层,直接观测副作用。这也是本例中能把23个Angular文件准确归因到critic模型的关键证据链。

坑三:模型选择悄悄决定了文件是否会被写入

修好运行时之后,drafter运行了两次,报告成功,却什么都没写。审计日志显示了原因:作者标记为"coder"的某个模型会将捏造的<tool>块作为纯文本输出,而不是执行真正的工具调用。harness看到的是文本,执行零个动作,磁盘上什么都没变。模型描述了写文件的过程,但从未真正执行。

这个模型是根据文件大小和口碑分配的。审计日志其实已经包含了预测这个失败所需的证据——统计哪些模型通过harness产生了真实的文件操作。两个模型产生了数百次真实操作,另外两个产生了零次。作者在重新分配之前没有查阅日志。

系统性缺陷在于:Agent下拉菜单提供某个模型,仅仅因为本地服务器拉取了它,没有任何工具使用能力探测。一个使用不支持工具调用的模型的drafter,会在整个迭代上限内产出自信但空洞的尝试,并且全程报告成功。

harness是包围模型的运行外壳,负责把模型输出解析成真实动作:识别<tool>块、调用对应函数、把结果回灌成下一轮上下文。它的行为高度依赖模型是否原生支持工具调用协议。若模型只是在文本里“写出”一个工具块,harness会把整段当普通字符串,不会执行任何操作,于是循环看起来在推进、报告也显示成功,但磁盘毫无变化。这类失败最难排查,因为模型的自述完全合理,只有审计日志中“零真实文件操作”的统计才会暴露真相。因此在把任何模型接入Agent循环前,最好用一次探针任务验证它能否真正驱动harness。

坑四:加载开销吃掉了81%的时间

drafter/critic循环在单次运行中不断交替角色——drafter、critic、drafter、critic。在本地运行时,每次模型切换都可能意味着冷加载。第一次运行中,81%的墙钟时间花在了模型加载上,而不是实际生成。运行时被配置为只保持一个模型驻留并永久固定。修复只需要两个环境变量,但洞察在于瓶颈是运行时配置,不是模型,也不是GPU。

模型驻留(resident)与冷加载是本地推理的关键配置项。当同时使用drafter和critic两个模型时,若运行时只能保持一个模型在显存中,每次角色交替都要卸载旧模型、从磁盘或内存重新加载新模型。24GB显存通常不足以同时容纳两个中等模型,于是切换成本被反复放大到墙钟时间的大头。解决方向通常包括:让运行时只保留一个模型常驻并固定,或通过环境变量调整驻留策略、量化精度或并发上限。值得注意的是,这类瓶颈在监控上表现为明显的加载时间占比,而不是GPU算力饱和,所以必须先看运行时配置再怀疑硬件。

什么确实有效

不过也不全是翻车故事。自动生成的验收标准确实很强——19个条目,每一条都可追溯到任务描述,在无提示情况下锁定。状态机从未产生非法转换,即使经历了反复的调度失败。可观测性才是让这一切可调试的根本:每个模型调用的JSONL审计日志,包含持续时间、token计数,以及最关键的真实目录快照file-action diff。这篇文章中几乎每个数字都直接来自那份日志。

作者在修复后重新运行流水线时,它在零人工干预的情况下交付了一个包含七个文件的站点——当用从不咨询循环的脚本在系统外部检查时,66个检查点中通过了64个。

最反直觉的教训

声明不等于验证。 drafter声称它写了文件。critic声称它评判了文件。任务声称它完成了。这些声明没有一个被磁盘上的实际发生情况所支撑——直到作者从记录的运行状态和目录快照中重建了整个执行过程。整个系统中最强的信号,status: done,最终被证明只是某一步骤裁决的副本,而不是对整体任务的检查。

如果你正在构建本地Agent循环,有三件事值得在信任它之前完成:

  1. 在调度层强制执行裁判角色,而不仅仅是在文档中描述。 一个无法写入的只读critic,是可信裁决与自相矛盾之间的区别。
  2. 对你实际使用的能力进行模型探测。 工具调用和结构化输出是两种不同的能力。"它是一个编码模型"并不构成它能驱动你的harness的证据。
  3. 把运行时配置当作头号嫌疑人。 当80%的时间花在加载上时,问题几乎从来不是模型本身。
#本地Agent循环#drafter critic架构#AI Agent自评#工具调用能力#Agent运行时配置#LLM验证#自主流水线#本地GPU推理
分享:

相关推荐