# 决定 AI agent 够不够可靠的，不是模型够不够强——是模型之外那一层，大多数人从没搭过

- URL: https://davidyin.dev/writing/harness-tame-llm/
- 作者: David Yin
- 发布: 2026-05-28
- 标签: x-article
- 摘要: 同样的 task，两个不同 session 跑出来的结果差了一个档次。

*封面 · 决定 AI agent 够不够可靠的，不是模型够不够强——是模型之外那一层（SKILL × agent × state × gate）*

同样的 task，两个不同 session 跑出来的结果差了一个档次。

你花了一个下午在 CLAUDE.md 里加了 20 条新规则。第二天，agent 跑得还是飘。

这个现象有个名字。HumanLayer 那篇 [Writing a good claude.md](https://www.humanlayer.dev/blog/writing-a-good-claude-md) 给了最冷的判断：Claude Code 自带的系统 prompt 本身就已经有 ~50 条 instruction（[Piebald-AI 的解包仓库](https://github.com/Piebald-AI/claude-code-system-prompts) 提供了可以一行行数出来的方法）。在此之上，你每加一条规则，其余所有规则的遵从质量都会**均匀下降一点点**。质量不是某几条失效，是**全部一起退化**。这不是 Claude 的 bug，是所有 LLM 的注意力预算在分薄。

解法不是把 CLAUDE.md 写得更精炼。精炼的 CLAUDE.md 顶多让你少亏几条。你把注意力一直放在了模型层，但能让 agent 稳定产出的，不在那里。**在模型之外那一层。**

这篇给你一个可以落地的四件套：**SKILL × agent × state × gate**。文末有可 fork 的目录骨架。

## 一、诊断：你的 CLAUDE.md 不是不够好，是位置错了

先把数据放出来，因为后面很多决定都得回来对照这几个数。

[NoLiMa benchmark](https://atlan.com/know/llm-context-window-limitations/)（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 年初的整理](https://codersera.com/blog/agents-md-vs-claude-md-vs-cursor-rules-comparison-2026/) 给出 AGENTS.md 超过 300 行时 agent recall 显著退化。原话是 "structure beats volume"。300 行不是建议值，是观察到的拐点：过了就掉。

第三个数，更狠的来自 Claude Code 自己的 [GitHub Issue #23620](https://github.com/anthropics/claude-code/issues/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](https://www.oreilly.com/radar/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](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)："像一本组织良好的手册，先是目录、再是具体章节、最后才是详细附录——让 Claude 只在需要时加载信息。" 关键词在"只在需要时"。Anthropic 的 [Agent Skills 文档](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) 进一步把 skill 描述成"目录 + 可执行代码 + 参考材料的组合"，本质是一个虚拟文件系统接口，不是字符串拼接。

**agent — 角色专用的 system prompt，定义"谁"执行任务，不是万能助手。**
角色越单一，system prompt 越短，遵从度越高。一个"什么都做"的 agent 必然在所有事上都不够好。这是 attention budget 决定的物理事实，不是 prompt 写得好不好的问题。

**state — 跨 session 的状态文件，不是对话内存。**
[Anthropic effective harnesses for long-running agents](https://www.anthropic.com/engineering/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](https://johnsonlee.io/en/2026/05/15/from-prompt-to-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 的路由入口，最小可工作字段就三行：

```yaml
---
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 就开始崩。

崩的原因有三层：

1. **session 结束 = 内存全清。** 下一个 session 的 agent 不知道上一步做了什么，你要么从头讲一遍（贵），要么让它瞎猜（飘）。
2. **长 session 里 context rot 会让 agent 忘掉早期信息。** 第 5 步还记得第 1 步定的约束的概率，在我自己的实测里大概只剩一半。
3. **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 文件其实就这么几个字段：

```markdown
---
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](https://www.anthropic.com/engineering/harness-design-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 只是我自己一种实现。

**位置错了，怎么卷都没用。**

<!-- revision: fc-round-1 + review-round-2, P0+P1 applied -->
