unlazy技能与深度树:防止代理提前宣布完成
了解unlazy如何用深度树、文件门槛和PLAN.md契约阻止代理提前收工,包含安装步骤、2.0.0与未发布2.1.0的版本差异,以及任务深度的实际成本。
unlazy 技能的作用
unlazy 技能旨在阻止一种问题:编码代理在工作完成前就报告任务已完成。它的核心是深度树(Depth Tree),这种方法按任务的实际边界拆分任务,并为每个叶节点设置独立的验收门槛。第 2 版将强制检查从说明文字移到了文件中,因此代理必须根据一组可运行的命令证明任务已完成,而不能只自行声称完成。
该项目由 Leonxlnx 发布在 github.com/Leonxlnx/unlazy,采用 MIT 许可证。项目没有可固定的带标签版本:截至 2026-10-11,仓库的 tags 列表和 releases 列表均为空。本指南最初依据 2026-08-10 发布的 2.0.0 源码里程碑编写。2026-10-11,我们再次根据 main 分支上的当前源码进行了检查。CHANGELOG 将该源码标记为“未发布,目标版本为 2.1.0”,并说明不会为 2.1.0 提供 Git tag 或 GitHub Release。今天运行安装命令会获取更新的源码,而不是 2.0.0。这个领域的技能变化很快,因此在将本文内容复制到长期运行的环境前,请先阅读仓库中的 CHANGELOG。
这里所说的技能符合通常定义:当任务与其描述匹配时,运行框架会将一个 SKILL.md 文件加载到模型的上下文中。如果你不熟悉这一机制,请先阅读什么是代理技能,以及运行框架如何加载技能。unlazy 由纯 Markdown 和少量 Node 脚本组成,因此自身不需要服务器或 API 密钥。
为什么代理会在完成80%时停止,并遗漏您的第3条指令
这种行为有明显的模式。您要求完成4件事。回复覆盖了第1件、第2件和第4件。结尾摘要却将4件事全部列为已完成。整个过程没有报错,因此也没有触发任何告警;您可能在1周后才发现遗漏。
unlazy README 将这一现象与已发表的模型惰性研究联系起来。它引用 arXiv 2512.20662 的结论:“详细的多部分提示在经过测试的模型中仍会出现部分遵循和提前截断。” gates 的设计前提表述得很明确:“上述规则是散文,而散文正是本项目已经视为最薄弱的层。”要求代理更加努力,只是在生成缺口的同一上下文中增加更多散文。
因此,version 2 将状态保存在文件中。GATES.md 中的复选框不受模型自行决定。门下要么有证据,要么没有证据;脚本无需询问代理,就能告诉您具体情况。
深度树,逐层展开
深度树是一种分解方法,并规定了拆分可以出现的位置。方法参考中写道:
只能在真实的领域边界、组件边界或验证边界处拆分。二叉拆分不是必需的。
将第 1 层设为请求的任务。
叶节点承载交付物。叶节点之上的每一层都会整合并验证子节点返回的结果。
叶节点不应只是一个项目符号。参考文档按结果而不是耗时来确定叶节点的规模:
每个叶节点都应对应一个连贯的交付物。
合并相邻的微小叶节点;如果一个叶节点隐藏了多个相互独立的结果,就拆分它。
这条规则可以防止深度树变成无意义的忙碌。如果一个叶节点写成“重命名变量”,它就无法通过测试,说明上一级拆分得多了一层。
每个叶节点随后执行四轮处理:完整实现,不保留占位符;以领域专家的视角重新阅读;查找缺陷;在成本较低时进行润色,并持续重复,直到完整的一轮改进未发现任何问题。微小叶节点需要合并,另一个原因就在这里。对一个两分钟的修改执行四轮处理,只是在做样子。
调用此技能时选择深度:
/unlazy tree 5 refactor the payment module也可以使用自然语言,因为该技能会根据意图匹配,而不是只匹配斜杠命令:
tree 3 build the landing page and do not stop until every gate is checked本指南的早期版本曾引用按深度划分的范围,并为每个深度指定叶节点数量。当前的方法参考完全没有叶节点数量,并明确写道:“不要把深度视为对工作量或令牌数的算术承诺。”现在只保留定性指导:“对于功能、范围明确的缺陷排查或文档,应使用浅层树或单人账本。”当多个连贯的交付物适合使用全新的上下文或由不同人员独立负责时,才适合使用编排树。只有当新增分支对应真实的集成边界时,才有理由使用更深的树。
如果不指定深度,参考文档要求“选择能够暴露每个独立交付物和集成点的最小树”。如果指定的数字与工作内容不匹配,它要求代理说明这一点,而不是凑数:“如果请求的深度会产生填充叶节点,请说明不匹配之处,并使用最接近实际情况的分解。”深度描述的是工作本身,因此增加深度并不会提升质量。
门禁文件的形式
在开始任何工作之前,代理会将验收标准写入门禁文件。每个门禁都是一个复选框,下面附带一条命令。
# Gates: pricing section
- [ ] G1: three tiers render with real copy
CHECK: node check.js pricing --tiers
EXPECT: 3/3 tiers ok
EVIDENCE: pending
- [ ] G2: annual toggle changes both price and label
CHECK: node check.js pricing --toggle
EXPECT: toggle ok
EVIDENCE: pendingCHECK 是命令。EXPECT 是判定通过的输出,同时命令的退出码也必须为 0 才算通过。EVIDENCE 从 pending 开始。运行通过后,检查器会将其填入输出指纹,而不是原始输出。手动门禁没有命令,因此需要记录能够证明结果的最小事实。该技能提供 scripts/gate-check.mjs 来完成此操作。使用 --status 时,它会在不运行命令或修改文件的情况下,报告仍未关闭的门禁,因此无需阅读执行记录即可审计一次运行。只有传入 --approve 时,它才会运行账本命令,因此在传入该参数前,应阅读每条命令及其调用的每个脚本。另有一个独立的 scripts/gate-lint.mjs,可在不运行任何内容的情况下检查账本质量。
SKILL.md 中的报告规则只有一行:
只要任何必需门禁尚未满足、已放弃、已延期或等待负责人决策,就不要编写完成报告。
此外,系统还要求代理“在报告前立即重新测量每个数值和完成情况声明”。门禁不能因为代理认为工作已经完成就关闭。关闭门禁需要执行一次运行,其输出必须匹配 EXPECT,否则就不能通过。
编写陌生人也能执行的门禁。“看起来没问题”不是检查。使用 test -s dist/index.html && echo ok 打印 ok 是检查,因为文件为空或不存在时,该检查会明确失败。
并行工作前的 PLAN.md 契约
当树状任务足够宽、叶节点在独立上下文中运行时,这些叶节点就不再共享假设。方法参考要求在分发任务前先定义契约:“在分发前修复契约。”计划在 PLAN.md 中列出所有必需的结果,“然后在叶节点启动前记录接口、格式、共享假设、错误约定、命名和所有权”。
原因很具体。分别要求两个子代理“添加错误处理”时,它们会设计出两种不同的错误结构。由于每个叶节点在本地都满足要求,两个叶节点都能通过各自的检查。问题只会在它们交互时出现。这个交互点属于上层的职责,编排参考对此有明确说明:“重新验证子节点,然后运行接口、端到端和回归检查。”
在编排模式下,要求驱动程序“只向每个叶节点提供共享契约、明确的所有权和依赖关系,以及该叶节点自己的台账”,并且“不要泄露其他叶节点无关的历史”。每个叶节点的台账都会声明它可以写入的路径,并且这些 OWNS 路径不能与同时分发的其他叶节点重叠。子代理返回后,驱动程序必须“重新运行所返回叶节点的可运行检查,包括已经检查过的检查”,因为仅有 --status 不等于重新验证。未提供实际证据、只是勾选了检查项的叶节点无法通过这次重新运行。
编排也有适用下限。编排参考要求:“如果一个专注的上下文可以在不隐藏独立交付物的情况下完成并验证任务,就保持单人处理。”每个子代理都必须从头重建对任务的理解。因此,对于小型任务,初始化成本可能超过额外专注力带来的收益。
如何安装 unlazy skill?
推荐使用 skills CLI:
npx skills add Leonxlnx/unlazy添加 -g 可按用户级别安装,添加 --all 可为 CLI 检测到的所有 agent 安装。该命令会安装当前 main 分支中的内容。项目没有带标签的版本发布,因此如果需要安装内容不随时间变化,应按 README 的说明固定到确切的 commit。
手动安装方式是将仓库克隆到 agent 的 skills 目录:
git clone https://github.com/Leonxlnx/unlazy ~/.claude/skills/unlazygit clone https://github.com/Leonxlnx/unlazy ~/.codex/skills/unlazy然后确认文件已写入目标位置:
ls ~/.claude/skills/unlazy/SKILL.md命令输出的路径表示文件已在磁盘上。No such file or directory 表示仓库被克隆到了其他位置,通常是因为你假设的 skills 目录名称不存在,git 因此创建了一个新目录。也不要只相信 agent 的判断。agent 声称 skill 已安装,并不代表它已经显示该文件。检查器和可选 hook 需要 Node 16 或更高版本,且不使用第三方运行时包。
截至 2026-10-11,README 记录了两种 harness:Claude Code 中使用 /unlazy 调用 skill,Codex CLI 中使用 $unlazy。README 没有说明不带 skill loader 的 harness 的使用方式,因此将 SKILL.md 粘贴到其他工具的规则文件中尚未经过测试。如果你想了解 SKILL.md 如何才能可靠加载,编写自己的 agent skill 介绍了决定 skill 是否触发的 frontmatter 和描述匹配规则。
Claude Code 之外的环境支持 Stop hook 吗?
不支持。这一点需要明确。上文全部是指令,而指令可能被忽略。唯一提供结构化强制机制的是 Stop hook,而 Stop hook 是 Claude Code 的功能。
node <path-to-skill>/scripts/install-hooks.mjs # this project only (settings.local.json)
node <path-to-skill>/scripts/install-hooks.mjs --global # every project
node <path-to-skill>/scripts/install-hooks.mjs --uninstallStop hook 会在代理尝试结束本轮时运行。此 hook 会扫描当前会话的账本和调度状态;如果仍有未满足的门禁条件,就阻止本轮结束,因此本轮不能在存在未检查项时结束。它不会执行检查,也不会自行调用模型。不过,它并不是免费的。token 经济性参考指出:“阻止结束会触发代理继续运行,这会消耗模型资源。”有关这一机制以及可以绑定的其他事件,请参阅Claude Code hook 如何在一轮运行期间触发。README 要求只有在用户同意后才能安装此 hook。它还要求将 .claude/settings.local.json、.unlazy/ 和 .unlazy-hook-state.json 保留在忽略规则中。
它还提供了一个释放机制,实际作用比表面上更重要。如果代理连续 6 次被阻止结束但门禁没有取得任何进展,hook 会释放代理,而不是将其困住;只修改元数据的编辑不会重置这个计数。ABANDON: <gate> <reason> 行也允许会话结束,但这表示一次可见的交接,而不是通过检查。被放弃的门禁会被报告为未成功结果,而不是已完成的工作。如果没有这两种退出机制,在你的环境中无法满足的门禁可能会持续消耗 token,直到你亲自终止会话。
在 Codex 或其他任何运行框架中,install-hooks.mjs 没有可供安装的目标,因为它只会写入 Claude 设置文件。你仍然可以获得门禁文件和可运行的检查,但没有结构化机制阻止模型提前结束本轮。在这些环境中,你必须自行阅读门禁文件。
unlazy 还是 ponytail:您需要哪一个?
这两个技能在同一时期受到关注,并且在工作量方面方向相反,因此很容易混淆。
ponytail 让代理表现得像一名资深开发者,首先会询问这段代码是否真的需要存在。它会缩小范围,优先使用标准库,并减少差异。unlazy 假定范围已经确定,并持续增加工作力度,直到范围内的每一部分都完成并经过验证。
因此,应根据您实际遇到的故障来选择。如果代理把一个小功能扩展成一个框架,您需要 ponytail 技能及其“懒惰资深开发者”角色。如果代理遗漏四部分请求中的第三项,却报告任务成功,您需要 unlazy。
可以同时运行两个技能,但顺序很重要。先用 ponytail 的问题确定范围,再将确定的范围交给 unlazy 的检查关卡。如果顺序相反,您会为 ponytail 原本会删除的工作构建一棵枝叶繁茂的工作树,然后为所有额外工作付出代价。这个顺序是我的建议,并不是两个项目之间有文档说明的集成方式。
VPS 上托管的代理会产生多少深度成本?
工作量以 token 计。在 VPS 上使用您自己的 API key 运行代理时,token 就意味着费用。unlazy 会增加多少费用,目前项目不再给出具体数字。
本指南早期版本根据作者进行的 6 次运行对比,列出了输出 token 和费用倍数。作者后来撤回了这些数据。references/token-economy.md 现在说明,相关原始提示、执行轨迹、输出和评分记录已不在代码仓库中,“因此这些数字无法在此复现”,并补充说:“不要将其用作产品保证。”方法参考进一步说明:“不要将深度视为关于工作量或 token 数量的算术承诺。”我们已删除这些数字,不再重复作者本人也不再支持的结果。
该参考文档目前对费用的说明仍是方向性的。开头的建议是:“将重复且确定性的验证移入命令,并缩小编排上下文。”单人模式会在一个上下文中额外消耗输出 token。编排模式会增加上下文数量,而且每个新上下文在执行任何有效工作前,都要读取契约及其自己的账本。该参考文档同时警告两种做法:“不要编排一个单个专注会话就能清晰实现和验证的任务。”它也警告,不要仅为避免子代理开销,就把整个构建过程放在一个上下文中,尤其是在叶节点和契约都已明确时。
由此可得出以下 4 项设置:
- 选择叶节点确实构成工作单元的最小深度,然后停止。不需要的深度,就是不需要的开销。
- 如果一个专注会话就能完成构建和验证,请使用单人模式,因为在这种情况下,子代理的初始化开销高于新上下文带来的收益。
- 如果您使用 Claude Code,请安装 Stop hook。该 hook 不会自行调用模型,但每次阻止停止后,都会让代理继续工作,而这部分工作会计费。
- 在开始任何长时间任务前,先在账户级别设置硬性费用上限。
最后一点最重要。一个设计为拒绝提前停止的技能,本质上就是一个会持续工作的技能。预算和告警与提示词是两项不同的工作;控制 VPS 上代理的费用介绍了应首先设置的限制措施。
作者测量了什么,以及这些结果能证明什么
该仓库过去会发布自己的测试结果,这种做法本来就比应有的更少见。后来,作者又撤回了这项测试,这种情况更为少见。README 现在只说明:“Earlier README versions also cited a six-run internal comparison”(README 的早期版本还引用过一项运行 6 次的内部比较),以及“复现这些确切比例和计数所需的原始产物不在此仓库中”。README 给出的建议是:“将这些结果视为历史设计输入,而不是基准测试保证。”
相关设置仍保留在 research/validation-protocol.md 中:2 个构建任务,每个任务包含 3 种条件(不使用 skill、tree 3、tree 6),每次运行都使用全新的文件夹和会话。该协议将其称为“由维护者运行的探索性比较”。协议说明,测试产生的 token 比例、自行发现的缺陷数、实时失败数和报告编号错误数,无法仅通过该仓库复现或审计。协议还说明,不得将该比较描述为 unlazy 导致了某项具体改进,或产生了某个成本倍数。本指南的早期版本曾绘制该测试每次运行的计数。鉴于作者已经撤回这些结果,我们已将其删除。
有一条设计经验不受这些数字变化的影响。SKILL.md 仍要求“在报告前立即重新测量每个数字和完成情况声明”,而 gates 模板则警告不要把外部提供的数字直接复制到 EXPECT 中,作为自己的证明。建立在损坏构建之上的自信总结,正是 gates 要防止的情况。
下面说明限制。截至 2026-10-11,已经没有可供评估的已发布结果,只有测试方法和一份用于正确重测的协议。请改为在自己的工作中测试:使用相同任务运行 2 次,一次不加配置,一次使用 gates 文件;然后统计已关闭且包含可由你自行重新运行的证据的 gates 数量。这个数量是这一领域中唯一真正属于你的数字。
FAQ
unlazy 技能中的 Depth Tree 是什么?
它是一种分解方法。任务只在实际的领域、组件或验证边界处分解,第 1 层是请求的任务,每个叶节点都是一个具有独立门禁的完整交付物。相邻且过于细小的叶节点会合并;隐藏多个独立结果的叶节点会再次拆分。叶节点之上的每一层都会负责集成和验证:重新检查子节点,然后执行接口、端到端和回归检查。调用技能时可以指定深度,例如 tree 5。如果未指定深度,该技能会选择能够暴露所有独立交付物和集成点的最小树。
unlazy 技能能在 Claude Code 之外运行吗?
部分可以。该技能使用普通 Markdown 编写,README 同时记录了 Codex CLI 和 Claude Code 的用法,因此 Depth Tree、门禁文件、可运行检查以及每个叶节点的四轮处理都可以在那里工作。当前文档未涵盖其他 harness。强制执行方式有所不同。在门禁未满足时阻止回合结束的 Stop hook 是 Claude Code 的功能,使用 node <path-to-skill>/scripts/install-hooks.mjs 安装。在其他环境中,没有任何结构性机制阻止模型提前结束回合,因此需要由您读取门禁文件并将其发回。
unlazy 技能会增加多少 token 费用?
目前没人能给出可靠的数字。作者之前提供的 token 比例来自 6 次无法复现的运行。当前文档说明不要将这些比例作为产品保证,也不要把深度当作对工作量或 token 数量的算术承诺。总体方向仍然明确。Solo 模式会在同一个上下文中增加输出。Orchestrated 模式会为每个叶节点创建一个新上下文,新上下文会在开始工作前读取契约及其台账。Stop hook 不会自行调用模型,但每次阻止都会让 agent 返回继续工作。请针对您自己的任务,并依据您自己的计费情况进行测量。
我应该使用 unlazy 还是 ponytail?
根据当前面对的故障选择技能。ponytail 适用于输出过多的 agent,因为它会模拟一名高级开发者,先询问代码是否确实需要存在,并优先使用标准库。unlazy 适用于没有完成您所要求内容的 agent,因为它会强制进行分解,并在没有证据时拒绝关闭门禁。如果您希望同时使用两者,先用 ponytail 确定范围,再将该范围交给 unlazy。这样可以避免为本应删除的工作支付额外的工作量倍数。
安装 unlazy 后,我的 agent 为什么仍会提前停止?
请检查 4 项内容。首先,使用 ls ~/.claude/skills/unlazy/SKILL.md 确认技能已写入磁盘,因为克隆到不存在的目录是最常见的遗漏。其次,确认工作开始前已经写入门禁文件,因为 hook 会扫描会话的台账,而缺少 GATES.md 时没有可阻止的内容。第三,确认 hook 的安装位置:直接运行 install-hooks.mjs 只会写入当前项目的 settings.local.json,因此其他项目需要使用 --global。第四,请记住释放机制正在按设计运行。连续 6 次被阻止且门禁没有进展后,agent 会被释放;而 ABANDON: <gate> <reason> 行会有意结束本次尝试,作为可见的交接结果,但这不表示成功。