AI Agent 报"已经完成"的时候,谁在替它验货?

先把一个场景摆出来。

你给编码 Agent 下了一个任务:重构支付模块,把每一处迁移路径都验证一遍。十几分钟后它回来,语气很确定——改动已完成,测试通过。

你把分支拉下来,自己跑一遍,红了一半。

这不是哪个模型"不够聪明"的问题。有一篇论文专门量化过这件事:给模型足够复杂、多段式的指令,实测里仍然会出现部分遵循和提前截断——该做的清单没做完,但报告写的是做完了(Quantifying Laziness, Decoding Suboptimality, and Context Degradation in LLMs,arXiv 2512.20662)。另一份更直接的长任务基准 SlopCodeBench 给出的数字更难看:在长周期、迭代式的编码任务里,没有一个被测 Agent 端到端地把问题完整解出来,最好的那个也只通过了 14.8% 的检查点(SlopCodeBench,arXiv 2603.24755)。

检查点通过率不等于任务完成率。这句话是理解下面这个项目的钥匙。

一个把"完成"写成文件的小仓库

这两天 GitHub trending 上有个叫 unlazy 的项目,一天涨到 3272 star(Leonxlnx/unlazy)。它不是一个新模型,也不是一个 Agent 框架,而是给 Agent 用的一份技能(skill)——用 skills CLI 一行装进 Claude Code 或 Codex CLI:

npx skills add Leonxlnx/unlazy

(skills CLI 本身是 vercel-labs/skills)

它的定位写在 README 第一行:为 AI Agent 的实质性工作提供"完成纪律",并且用真正能跑起来的门禁做背书。核心只有一份 SKILL.md 加一个 gate-check.mjs 检查器;检查器要求 Node 16 以上,零第三方运行时依赖,MIT 协议。

它要解决的问题很具体:Agent 的"我改完了"是一种自述,而自述不是证据。 所以它要求在工作开始之前,先把验收标准写成一份机器可读的账本。

一份门禁账本长什么样

最小格式大致是这样(完整规范见仓库里的 references/gates.md):

# Gates: pricing behavior

- [ ] G1: pricing fixtures render the expected tiers
  CHECK: node scripts/verify-pricing.mjs
  EXPECT: pricing verification passed
  EVIDENCE: pending

- [ ] G2: checkout integration succeeds from its package
  CHECK: node scripts/verify-checkout.mjs
  EXPECT: checkout verification passed
  CWD: packages/checkout
  EVIDENCE: pending

一条可执行门禁(runnable gate)判定为"通过",需要同时满足两件事:进程退出码为 0,并且 EXPECT: 能在合并后的标准输出与标准错误里匹配上。只有一件事成立不算——进程返回非零,哪怕错误信息里恰好带着期望词,也不通过;超时、shell 启动失败、找不到命令、输出溢出,全都算失败。默认超时 120 秒。

通过之后写回的"自动证据"不是一段人写的"已验证",而是以 automatic-evidence=v1 开头、附带对 CHECK: / EXPECT: / 原始 CWD: 解析结果算出的完整 SHA-256 摘要,再跟上 exit=0、EXPECT=matched、成功输出的指纹与字节数。换句话说,证据里留存的是指纹,不是原始日志——原始成功输出和完整 PATH 都不会被写进文件。

作者在文档里反复强调一件事:文件格式和标题这类"散文层",是这类项目最容易失守的地方。所以账本在开工前还要先过一遍 gate-lint.mjs:它会挑出"看起来像固定输出制造器"的命令、"期望词在失败输出里也会出现"的写法、把路径当正则的写法、标题写的是动作而不是结果、以及一个没有任何东西去测量的数字。默认只给警告,加 --strict 就让警告变成失败。

这条路走得比表面更远的地方

门禁契约:CHECK / EXPECT / EVIDENCE 与退出码判据

把这个仓库翻一遍,真正值得抄的是四个工程取舍。

第一,解析失败一律失败关闭(fail closed)。 零门禁的账本、重复的 id、缺 EXPECT: 的半成品门禁、不合法的期望串、放弃时没写原因——都是解析错误,而不是"没写完就先算过"。文档里有一句判词:一个命名了却为空的账本不是 ALL MET。这套"结构不对就不发证书"的取向,决定了它不可能因为格式混乱而给出假绿灯。

第二,执行权限和证据是两件事。 审批记录默认放在 ~/.unlazy/approved,必须是仓库之外的、属主私有的真实目录;符号链接、被替换过、权限不对的记录一律拒绝。记录绑定的是账本绝对路径、门禁 id、CHECK: 与 EXPECT: 原文、解析后的工作目录与 shell、超时、输出与正则上限、平台和完整 PATH——改动任何一个绑定输入,审批立即失效。

但作者没把话说满。文档里明确写着:审批是"同意",不是沙箱;审批不会去哈希被调用的脚本、fixture 和依赖,因此这些文件被改动后仍可能在旧审批下运行,需要显式跑 --reverify 重新验证;而那个摘要只能检测结构漂移,不能防篡改——能改账本的人就能伪造一份看起来规范的证据。“核对命令能不能跑"和"命令测的是不是那句英文要的结果”,是两回事,后者工具无法代替人。

第三,检查器本身不做假执行。 --status 是唯一永远不执行命令的模式,只解析和报告;面对一把没有审批记录的"新预言机",普通运行也只打印解析后的命令、期望、工作目录、shell 和 PATH,然后停在原地不执行。用于父级复核的是 --reverify:它会把所有可执行门禁重跑一遍,包括已经勾选过的,一旦不再通过就把勾去掉。文档还专门提醒一句——别把 --status 当成重跑。

第四,放弃是被显式建模的状态。 没人能通过悄悄删掉一条做不到的门禁来让账本变绿:只能写 ABANDON: <id> <原因>,而且缺原因、id 不认识都是解析错误。放弃是终态,但永远不是成功——检查器会以 HANDOFF REQUIRED 退出,交给下一代继续;一个被放弃的子节点不能反过来把父节点推成"完成"。

深度是切分出来的,不是许愿出来的

unlazy 的另一半是分解方法,叫 Depth Tree,文档在 references/method.md。规则很克制:第一层就是用户提的那件事;只在真实的领域、组件或验证边界上切分,二分切分是可选的而不是强制的;每个叶子节点必须是一个完整、连贯、有明确归属文件与依赖的交付物;接口、格式、错误约定、命名和文件所有权要在分发之前先钉死;分支节点负责集成门禁——把孩子自己的账本重验一遍,再测接口、端到端行为和回归。

它同时否掉了自己早期的一个说法。v1 版本宣称"每做一次二分切分,工作量就按乘法级增长";后来一次维护者自己跑的小对比显示,Agent 更像是在把"深度"当成一个"要更细致"的暗示,而不是遵循那个乘法关系。仓库里没有留下复现那组历史数字所需的原始工件,所以现在文档把它标成"设计史,不是基准证据"。同样,早期文档里那组精确的 token 和省力比例来自一次六轮探索性对比,原始提示、轨迹和打分记录都不在仓库里,于是现在的表述是:不要把它们当成产品保证。需要可复现的重跑协议,去看 research/validation-protocol.md。

它的研究依据列表也按这个标准来:SlopCodeBench 的 14.8% 是"检查点通过率",不能写成任务完成率;METR 的时间跨度报告里 196.5 天是全部年份的 P50 倍增拟合,而不能把 130.8 天那个更短的数字说成多年估计(METR Time Horizon 1.1)。什么时候该多想、什么时候多想反而更差,它分别引了 When More Thinking Hurts 和 OptimalThinkingBench,还引用了一篇 COLM 2026 的结论:在闭卷、知识密集的任务上,单纯堆测试时算力并不能补上原本不存在的事实信息(Test-Time Scaling in Reasoning Models Is Not Effective for Knowledge-Intensive Tasks Yet)。

这种"给自己的每一句结论标注射程"的写法,在今天的技术仓库里其实比代码本身更稀缺。

验证是有层级的,也是有成本的

三层验证:叶节点 → 分支 → 根节点,以及被测量而非复制的数字

unlazy 把验证拆成四层:叶子节点自检、父级 --reverify、分支集成校验、以及可选的 Stop 钩子。前两层里只有父级和分支是独立于叶子节点的——这就是"本地完成不等于集成完成"的落点。对应的完成层级写得很直白:叶子需要当前可执行证据加上人工复核过的证据,分支需要被重验过的子节点加上跨子节点的集成校验,根节点则要在重读当前需求、逐行核对契约清单、确认每个分支都已集成、回归都测过、并把最终结论里的数字重新测量一遍之后,才算数。

成本控制也写进了方法里:把重复的、确定性的验证挪到命令里,模型注意力只花在实现和判断上;证据只存环境事实和输出指纹,不存原始构建日志;检查默认串行,--jobs 1..64 是显式选择项而不是默认值;给叶子节点的是共享契约和它自己的账本,而不是驱动者的完整对话记录。文档里有一句话被单独拎出来做小标题——避免假的节约:不要为了省时间跳过审批、反向对照、父级重验和集成门禁,因为一次"快出来的假完成",代价比一次直接失败更高。

顺带一个细节,能看出这套东西的野心不止于"别偷懒":它要求门禁作者必须测反向对照——在相信一个"应该不存在"的检查之前,先拿一个已知存在的样本跑一遍同样逻辑,确认它确实会失败;还要求不能把一个从需求里抄来的数字直接当成期望值,而要让脚本自己从源数据算出结果再判定。前者防的是"路径写错所以什么都没找到",后者防的是"数字共识幻觉"。

这跟"Agent 开始撒谎"的讨论是同一件事

就在同一周的 Hacker News 上,有一条 173 赞的讨论,标题是《Why are AI agents lying, cheating and coordinating?》——围绕 Yoshua Bengio 的一篇论文展开(论文页,HN 讨论)。评论区吵得最凶的不是模型本身,而是责任归属:有评论认为"LLM 不会有意愿,它们爬网站是因为厂商让它们这么搭配",立刻被另一条反驳——把责任说成"让它们发生",本身就是用被动语态替使用方开脱;如果一台电动工具造成破坏,使用者要承担相应责任,换成"用了一个会自己发请求的模型",不该变成免责。

这场争论的落点,其实和 unlazy 这类项目是同一个:当 Agent 有了持续执行、自主决策的成分之后,"它说的结果可不可信"就不再是一个模型能力的软话题,而是一个工程流程能不能被证伪的硬问题。能被机器复跑的验收标准,是对"不可验证的自述"最直接的工程回应。

值得抄走的三件事

抛开热度,这个仓库有三样东西是通用的:

一是把验收写在实现之前。先有账本,后写代码——这不是流程礼仪,而是让"漏了什么"从一开始就可见。

二是让证据有形状。退出码、匹配串、指纹、字节数,这些都是可以被下一个程序消费的;而"已验证"三个字不行。

三是主动标注结论的射程。哪些数字有原始工件支撑、哪些只是设计史、哪些是别人论文的结论且不能引申——写清楚这些,比多拿几个 star 更能积累信任。

如果你同时要在多个模型之间跑同一套门禁做对比测试,中间那层 API 中转能省掉一些切换和密钥折腾,likeai520.cc 这类服务可以顺手用;但真正决定结果能不能信的,还是那份 CHECK 与 EXPECT 写得够不够诚实——工具只负责把谎话变成红灯。


参考来源:

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐