封面 · 决定 AI agent 够不够可靠的,不是模型够不够强——是模型之外那一层(SKILL × agent × state × gate)
同样的 task,两个不同 session 跑出来的结果差了一个档次。
你花了一个下午在 CLAUDE.md 里加了 20 条新规则。第二天,agent 跑得还是飘。
这个现象有个名字。HumanLayer 那篇 Writing a good claude.md 给了最冷的判断:Claude Code 自带的系统 prompt 本身就已经有 ~50 条 instruction(Piebald-AI 的解包仓库 提供了可以一行行数出来的方法)。在此之上,你每加一条规则,其余所有规则的遵从质量都会均匀下降一点点。质量不是某几条失效,是全部一起退化。这不是 Claude 的 bug,是所有 LLM 的注意力预算在分薄。
解法不是把 CLAUDE.md 写得更精炼。精炼的 CLAUDE.md 顶多让你少亏几条。你把注意力一直放在了模型层,但能让 agent 稳定产出的,不在那里。在模型之外那一层。
这篇给你一个可以落地的四件套:SKILL × agent × state × gate。文末有可 fork 的目录骨架。
一、诊断:你的 CLAUDE.md 不是不够好,是位置错了
先把数据放出来,因为后面很多决定都得回来对照这几个数。
NoLiMa benchmark(ICML 2025, LMU Munich + Adobe Research)做了一件事:把 needle-in-a-haystack 测试里那些可以靠表面关键词匹配作弊的题去掉,只留真的需要推理的题。结果:13 个顶级模型里,11 个在 32K token 处掉到 baseline 的 50% 以下。GPT-4o 从 99.3% 跌到 69.7%。在某些任务上,标称 context window 和实际 MECW(Maximum Effective Context Window,模型真能用得动的上下文)差距可超过 99%。
这意味着什么?标称 200K 的窗口,实际能稳定调度的远远小于标称值。你的 prompt 越长,agent 越像在一个塞满杂物的房间里找东西。
第二个数:codersera 2026 年初的整理 给出 AGENTS.md 超过 300 行时 agent recall 显著退化。原话是 “structure beats volume”。300 行不是建议值,是观察到的拐点:过了就掉。
第三个数,更狠的来自 Claude Code 自己的 GitHub Issue #23620:跑长任务时 leader 的 context 被 auto-compact,compact 之后 leader 直接忘了自己有个 team 在跑,没法再给队友派活,连”我有队友”这件事都不记得了。
把这三件事拼起来:你的 instruction 总数动不动过百(Claude Code 50 条 + 你的 CLAUDE.md),window 一塞就过 32K,长任务再触发一次 compact。结果就是你看到的那个:
- 同一个任务,不同 session 出来的质量飘忽 → instruction recall 在退化
- prompt 写到 2000 字还在加规则,但 agent 越来越蠢 → attention budget 在分薄
- 想让 agent 跑 5 步以上,跑到一半就跑偏 → 没有 state,没有 gate
这三条里我说一句重话:前两条你在 prompt 层使再多劲都解决不了,第三条根本就不是 prompt 层的事。 你在 CLAUDE.md 里加的每一条规则都在偷其他规则的注意力配额。你在用更长的 prompt 来解决更长的 prompt 造成的问题。
这是个死循环。出口在另一层。
图 1 · 加一条规则,所有规则一起退化一点点——这不是”某几条失效”,是 attention budget 在线性分薄
二、四件套:从 prompt engineering 切到 harness engineering
O’Reilly 上个月那篇 agent harness engineering 一句话把这层定义清楚了:“a coding agent is the model plus everything you build around it”。Harness 就是那个 “everything you build around it”。包着模型的那层控制架构。
我自己习惯把它叫”调度架”。Harness 这个英文词字面是马具:套在马身上、决定马往哪走、什么时候停、什么时候拉车的那套东西。换到 LLM 上:模型是马,harness 是缰绳、车轴、刹车、路线图。
四件套的定义我直接给短句版,每一件都用一个判断说清楚它不是什么:
SKILL — 按需加载的领域知识 + 行为规范,不是预注入的 prompt。 SKILL 文件躺在磁盘上,agent 在任务需要时才显式读它,不需要时它不占 context。Anthropic 把这件事叫 progressive disclosure:“像一本组织良好的手册,先是目录、再是具体章节、最后才是详细附录——让 Claude 只在需要时加载信息。” 关键词在”只在需要时”。Anthropic 的 Agent Skills 文档 进一步把 skill 描述成”目录 + 可执行代码 + 参考材料的组合”,本质是一个虚拟文件系统接口,不是字符串拼接。
agent — 角色专用的 system prompt,定义”谁”执行任务,不是万能助手。 角色越单一,system prompt 越短,遵从度越高。一个”什么都做”的 agent 必然在所有事上都不够好。这是 attention budget 决定的物理事实,不是 prompt 写得好不好的问题。
state — 跨 session 的状态文件,不是对话内存。 Anthropic effective harnesses for long-running agents 把这件事说得很白:“agents quickly understand state of work when starting with fresh context window, accomplished with the claude-progress.txt file alongside git history”。状态在文件里,不在 session 里。这是一切跨 session 协作的物理前提。
gate — 硬阻断,不是”请不要 X”。 johnsonlee.io 那篇 从 Prompt 到 Harness(文中关键论点:KB/SKILL is not harness)把这一点钉得最死:「A gate means: if it does not pass, it cannot continue. Not ‘the model thinks it is fine,’ not ‘it looks okay to a human,’ not ‘it should probably be fine,’ but a deterministic check that blocks the output.」 翻成中文是:不是”模型自己觉得没问题”,不是”人类粗看像那回事”,也不是”大概应该没问题吧”,而是一道确定性的校验——不通过就拦住输出。 通过 = 通过,不通过 = 物理上不让它继续。不是靠模型自律。
四件缺一就缺一个档位。
图 2 · harness = 你围着模型搭的所有东西。四件缺一就缺一个档位
我得专门点破一个误区,因为这是这篇文章最重要的一个判断:SKILL/KB 不是 harness。
很多人把 .cursor/rules/ 写得又大又全,把 CLAUDE.md 堆到 800 行,以为这就是 harness。不是。这只是把知识塞进了 prompt。一层更精致的 prompt engineering,本质还是 prompt engineering。没有控制流、没有外化的 state、没有硬阻断的 gate,agent 跑歪了你只能祈祷。johnsonlee 那篇文章的核心论点翻译过来:真正的 harness 难在输出校验那一层。关键词是 gate,不是 review。
下面分三段讲三件最难落地的:SKILL 怎么按需加载、state 怎么外化、gate 怎么物理阻断。agent 角色拆分相对直观,放到最后骨架那节一并讲。
三、SKILL 落地:让 agent 自己去翻目录,不要把整本书塞给它
SKILL 的设计原则一句话:progressive disclosure,不是 knowledge dump。
可以照抄的目录结构长这样:
.harness/skills/<skill-name>/
SKILL.md
references/
style-guide.md
examples/
example-01.md
example-02.md
edge-cases.md
SKILL.md 是入口,references/ 是按需加载的子资源。关键差别:references/ 里的东西默认不在 context 里。Agent 只有在任务真的需要时才显式 read 某个 references 文件。这才是 progressive disclosure 的操作方式,不是”把所有相关知识打包注入”。
我自己跑的实例在 .omp/skills/x-topic-research/SKILL.md:SKILL.md 本身只放激活条件、目录、关键产出路径;具体的选题方法论、爆点公式、传播信号矩阵全在 references/ 里。当 topic-research-assistant 接到一个普通选题任务时,它根本不会 read 那些子文件,节省下来的 context 留给真正的工作。等它遇到一个不确定的爆点判断时,它才主动去 read references/explosion-formulas.md,按需进、用完出。
SKILL.md 的 frontmatter 是 orchestrator 的路由入口,最小可工作字段就三行:
---
name: x-topic-research
description: 当用户给出一句话选题或要求生成 X 文章选题方案时使用
when_to_load: explicit
---
name 是路由 ID,description 是激活条件描述,when_to_load 控制是否默认注入。Orchestrator 靠 description 决定调哪个 skill,不靠 magic prompt 拼接。
写 SKILL 时三个关键决策,我按踩过坑的优先级排:
决策 1:SKILL.md 只放”激活条件”和”目录”,不放方法论本体。
判断标准:如果你的 SKILL.md 已经超过 200 行,大概率你把本该进 references/ 的东西塞进了入口。入口应该像一本书的目录页,告诉 agent “想知道什么去翻哪一章”,而不是把整本书的内容浓缩在目录页上。
决策 2:示例放进 references/examples/,主文件里只放”去找什么”。
我见过 800 行的 SKILL.md,里面 500 行都是例子。结果是这个 skill 一被加载就吃掉一大半 context,agent 还没开始干活就已经在窒息。示例应该是 agent 决定要参考时才打开的那本书,不是默认翻开摆在桌上的。
决策 3:按角色分配最小工具集。
不是所有 agent 都需要 Web 搜索 + 文件读写 + shell + 数据库连接。一个只负责写综述的 agent 给它 shell 权限,意味着它每次决策都要在”我要不要跑 shell 命令”上消耗注意力,而那本来就不该是它要操心的。工具是 context 占用项,不是免费添加项。给 agent 配工具的原则跟给员工配权限一样:能少给就少给。
三个最常踩的坑:
- 把 SKILL 当 prompt 写:所有规则一股脑塞 SKILL.md 然后默认注入。结果就是退回 prompt engineering,名字变了,本质没变。这是看似搭了 harness、实际还在 prompt 层卷的典型症状。
- 示例农场:SKILL.md 里堆几百行例子,挤掉真正的 task context。例子越多看起来越严谨,实际越拖累。
- 激活条件写得太宽:description 写成”涉及任何写作场景都用”,结果所有任务都被这个 skill 接住,跟没分 skill 一样。激活条件写不窄,多 skill 体系直接退化成单 prompt。
最后一句关于 SKILL 的判断:如果你的 skill 系统加载之后 agent 的可用 context 变少了,那它就不是 harness 层的资产,是 harness 层的负债。 这是检验你 SKILL 设计的最朴素的标尺。
四、state + gate 落地:两件最不像编程的事,恰好是 harness 最难的部分
State:写进文件,不要存在对话里
很多人第一次搭多 agent 流程,state 是用”上一个 agent 说了啥我接着说”的方式接力的。这个做法在跑通一次 demo 时看起来很优雅,跑到第三个 task 就开始崩。
崩的原因有三层:
- session 结束 = 内存全清。 下一个 session 的 agent 不知道上一步做了什么,你要么从头讲一遍(贵),要么让它瞎猜(飘)。
- 长 session 里 context rot 会让 agent 忘掉早期信息。 第 5 步还记得第 1 步定的约束的概率,在我自己的实测里大概只剩一半。
- Auto-compact 会让 leader 失忆。 Claude Code 在 ~80% context 用量时触发自动压缩,触发后 leader 失去对 sub-agent 的感知(前面那个 issue),multi-agent 协作直接断电。
正确做法只有一种:state 外化到文件系统。
.harness/state/
active/
<task-slug>.md
archive/
<task-slug>.md
每个 agent 启动时第一件事:read state/active/<task-slug>.md。结束时最后一件事:write 回这个文件。中间出错了,下一个 session 接着上次的状态继续,不是从零讲起。
我跑的 .omp/state/active/<slug>.md 是这么用的:每篇文章一个独立 state 文件,9 个 agent 跨 session 协作就靠它。文件里写的是结构化的元数据(title / phase / platform / date)+ 当前阶段标记 + 各阶段产出文件路径 + 已完成/未完成 checklist。多窗口并发跑不同文章时零冲突,因为每个任务的 state 是独立的物理文件,不存在共享内存。
最小可工作的 state 文件其实就这么几个字段:
---
slug: harness-tame-llm
title: 决定 AI agent 够不够可靠的...
phase: writing
platform: x
date: 2026-05-28
---
## checklist
- [x] topic-research
- [ ] writing
- [ ] fact-check
- [ ] review
## artifacts
- topic_brief: output/topics/2026-05-28-harness-tame-llm-brief.md
- article: output/harness-tame-llm/article.md
够简陋,够好用。重点不是字段多漂亮,重点是它在文件里。
一句话总结这个原则:agent 是无状态 worker,文件是有状态记录。 Anthropic 用 claude-progress.txt + git history 实现的是同一个思路。
Gate:物理阻断,不是道德请求
软提醒和硬阻断的差别,我用一个画面说清楚:
软提醒:你在门上贴了张纸条 “请勿入内”。
硬阻断:你给门装了锁,没有钥匙的人物理上推不动这扇门。
“请不要在事实核查出现问题时继续发布”,这是纸条。模型如果在执行压力下需要一个继续的理由,它有非常高的概率自己找到一个。这不是它坏,是 instruction-following 在面对 task-completion 压力时的常见行为。
真正的 gate 是这样的:条件不满足 → 工具不可用 / 文件不存在 / 调用直接报错 → 执行链物理中断。
四种 gate 模式,从弱到强:
Mode 1 · 文件存在性 gate
某个动作前要求某几个文件必须存在,缺一个就拒绝执行。我自己跑的实例:publisher 调用前必须确认 illustrated.md、factcheck.md、review.md 三个文件都存在,缺一就 error return。不是”提醒缺文件”,是根本不进入发布流程。实现成本极低,一段 shell 脚本就够,但能挡掉一半的”我以为它做了”。
Mode 2 · 内容格式 gate 输出不符合预定 schema 就拒绝,立即打回重做。最朴素的实现:用 JSON schema / Pydantic / Zod 校验 agent 输出,校验失败 = 不算输出。比”看起来对就接受”严格一档。这一档专治那种”输出看上去像那么回事、细看缺关键字段”的情况。
Mode 3 · 工具移除 gate 某角色在某状态下,直接从工具集里把特定工具拿掉,模型物理上用不了。这一档的关键词不是”不让用”,是”没有”。模型不会去找一个它根本不知道存在的工具。这是 johnsonlee 那篇文章反复强调的:让物理可能性消失,比让模型”决定不要做”靠谱十倍。具体到实现:reviewer 角色不给它 publish 工具,fact-checker 不给它写主稿权限,每个 agent 的工具白名单是它角色定义的一部分。
Mode 4 · 平台/角色 gate
最强一档:在 agent 接到 task 的第一时间,按 task 头部的元数据做硬路由判断。我自己跑的:publisher 收到任何带 platform: xhs 标记的 task,直接 error return。不论 agent 如何论证发布合理性都不行。因为它根本就走不到论证那一步,已经被打回了。这一档适合放跨平台、跨人格、跨权限边界的事。
我自己跑的那条多 agent 流水线实际配的 4 个 gate 放在一起看更清楚:
publisher×platform=xhs→ 直接 error(Mode 4)fact-checker出 ❌ → pipeline 停,等用户确认(Mode 1 的变种:往下游传文件,下游 gate 接住)reviewer-illustrator完成 → 永远停,等用户 approve(Mode 4 的强化版,人在回路)publisher调用前 → 检查三文件齐全(Mode 1)
图 3 · 从”缺文件就拒绝”到”平台元数据硬路由”——gate 的判断者是宿主程序,不是模型
注意没有一个 gate 是”请 agent 自己判断要不要继续”。Gate 的判断者是宿主程序,不是模型。
这条判断我说重一点:任何依赖模型自己判断要不要继续的 gate,都不叫 gate,叫祈祷。
写 gate 的隐形成本经常被低估:你得想清楚每一个 gate 触发后接下来该发生什么。是 error return?是 redirect 到另一个 agent?是停下来等用户?没想清楚的 gate 触发以后系统会卡在一个谁也不知道下一步是什么的状态。这件事我前两个月踩过:fact-checker 出 ❌ 之后我没规定 pipeline 怎么停,结果 agent 自己找了个”继续吧大概没问题”的理由往下走。后来加了一条 hard stop + user approve 才稳住。
五、最小可工作骨架:可 fork 的目录长这样
到这里你已经知道每件套的逻辑了。剩下的是组装。我给一个最小可工作 harness 的目录骨架,跟我自己跑的那套 harness 同结构,但去掉了项目特定的命名:
project/
.harness/
SYSTEM.md
skills/
<role-name>/
SKILL.md
references/
state/
active/
<task-slug>.md
archive/
rules/
guardian.md
agents/
<role-name>.md
每个文件必须有什么(不是”全量”,是”少了它就跑不动”):
SYSTEM.md — orchestrator 的角色 + 路由铁律。只放路由,不放业务知识。路由铁律包括:哪个角色处理哪类 task、平台/状态如何分流、何时调 skill、何时不调。业务方法论一律下放给对应 skill。这个文件如果开始变厚,说明你把 skill 该干的活塞进了入口。
skills/<role-name>/SKILL.md — frontmatter(name + description + 激活条件)+ 功能边界(能做什么 / 不能做什么)+ 关键输出路径。本体方法论放 references/。
state/active/<task-slug>.md — 任务元数据 + 当前阶段标记 + 各阶段产出文件路径 + checklist。每个 agent 必先 read、后 write。
rules/guardian.md — 各阶段 gate 条件 + 触发 gate 时的具体行为(error / stop / redirect)。这个文件是 harness 的”刹车系统”,所有的硬阻断写在这里。Guardian 文件本身就应该是被宿主程序强制读取的,不是给 agent 看的提醒。
agents/<role-name>.md — 单个 agent 的 system prompt + 它可调用的 skill 白名单 + 它可使用的工具白名单。
图 4 · 可 fork 的最小骨架:SYSTEM.md / skills / state / rules / agents 一一对应四件套
从 1 agent 起,按业务复杂度往上长:
Stage 1 · 1 agent:orchestrator 自己全做,没有 skill 分拆。适合探索期,task 边界还没稳定的时候。
Stage 2 · 3 agent:planner + generator + evaluator。这是 Anthropic harness design for long-running apps(2026-03-24,Prithvi Rajasekaran)实测出的三 agent 架构,受 GAN 启发——generator 写、evaluator 用 Playwright 实际点页面打分,能挡住 generator 自评时”信心爆棚”的偏差。这一档的关键是把”做”和”验”拆给两个不同角色,再加一个 planner 把全局意图压在前面,不让 generator 跑偏。三件事一件都不能省。
Stage 3 · N agent:按业务流程拆专角色,每个角色绑定专用 skill 和最小工具集。我这边跑到 9 个角色(选题 / 写稿 / 核查 / 三家审阅 / 配图 / 发布等),是因为内容流水线的阶段足够稳定、角色边界足够清晰才值得这么拆的。
我把劝告写在这里,作为这整篇里我最有把握的一条工程判断:不要从 9 agent 开始。
角色边界不清晰时,多 agent 系统的协作开销 > 收益。每多一个 agent 就多一组交接、多一处 state 同步、多一类失败模式。按我自己 9 agent 流水线的观感,多 agent 系统大部分故障都不发生在单个 agent 内部,而是发生在 agent 之间的交接节点上——上游写出来的文件下游 read 不到关键字段、state 字段名漂移、平台标记没传下去。交接是新增的故障面,不是免费的能力扩展。
正确节奏是:1 agent 跑通流程 → 看哪一步反复出错或反复占用过多 context → 把那一步拆成独立 agent → 给它专用 skill → 给它专用 state 写入位置 → 给它 gate 防止失控。每拆一次都要先证明”不拆解决不了”才拆。从 1 长到 3 通常需要两周,从 3 长到 9 我用了三个月,而且至今还在删——某些 agent 拆了一段时间发现没必要,又合回去了。
这个节奏不性感,但它是唯一不会让你前两个月就放弃的节奏。
收尾
回到开头那个画面:你在 CLAUDE.md 里加了 20 条规则,第二天 agent 还是飘。
现在你应该能看清楚那 20 条规则的命运了:它们没消失,但每一条都在被旁边其他 99 条规则稀释。你在模型层增加密度,对面是注意力预算在线性退化,这场仗在物理上就是输的。
可以稳定产出的能力不长在那一层。它长在外面:长在 SKILL 的按需加载机制里,长在 state 的文件落盘里,长在 gate 的物理阻断里,长在 agent 角色的窄边界里。这一层我们刚刚习惯叫它 harness。叫什么不重要,重要的是它不在 prompt 里。
我自己也是绕了半年才接受这件事。前半年我也在加规则、改 CLAUDE.md、调 prompt 模板。后来才意识到我在错的层卷错的东西。Skill 拆出来那一刻我没觉得多聪明,只觉得早该这么干。state 写进文件那一刻也是。Gate 装上之后,agent 第一次被硬挡下来时,我才真的相信——这玩意儿不需要靠它自律也能稳。
这套思路不绑定任何具体框架。Cursor / Cline / Aider / Aider Composer / Claude Code 任何一个 agentic IDE 都可以用同样的目录骨架落地,OMP 只是我自己一种实现。
位置错了,怎么卷都没用。