基于模型能力打造 Harness:一套诊断与迭代框架

Agent = Model + Harness。Harness 是控制 Agent 行为最直接的杠杆,但大多数团队设计 Harness 的方式仍然是”看到别人用什么我们就用什么”。本文提出一套诊断框架,帮你找到特定模型的真实弱点,再决定该加什么 Harness 组件。


一、一个根本矛盾

看看目前主流的三个 Coding Agent 产品:

Claude Code(Anthropic)

  • 极度精简的 Harness,依赖 Claude 本身的能力
  • XML output contract + 有限的 Hook 系统(AGENTS.md)
  • 几乎没有上下文压缩——假设 Claude 的 recall 足够好
  • 设计假设:模型足够强,Harness 只做最小约束

OpenAI Codex CLI

  • Rust 实现,多 Agent 架构(orchestrator + workers)
  • 结构化轨迹日志,审批门控
  • 更强的 Tool registry,明确的执行策略
  • 设计假设:模型需要结构性引导,但不需要过度保护

Gemini CLI(Google)

  • 深度绑定 Gemini 模型特性
  • 不同的上下文窗口管理策略
  • 与 Google 生态的集成
  • 设计假设:利用模型特有的能力边界来设计交互方式

三个产品都在做”给 LLM 套 Harness”,但设计分歧反映了底层模型的差异。

这引出一个本质的问题:

如何系统性地判断一个模型需要什么强度的 Harness?

不是在 README 里写”支持多模型”,而是说出:“对于这个模型,你应该开 JSON repair,关 compaction,把 tool 执行改成 sequential。“


二、核心观点:模型对自己的行为没有特权访问

先做一个实验:

问一个 LLM:“你喜欢 read 还是 edit 工具?”

它大概率回答:“edit,因为它更精准。” 但如果你去统计它 100 次实际 tool call——如果 write 出现在它的训练数据里更频繁,如果 edit 的 patch 一旦冲突它不知道怎么办——它可能实际更倾向于 write

self-report 和 behavior 之间的 gap 来自两条不同的路径:

self-report → 陈述性知识:"好的 agent 应该先用 read 看文件,再用 edit 精确修改"
behavior → 程序性知识:当前上下文、之前 tool result 的状态、哪条路径 token cost 最低

两条路径可能在训练数据里就脱节了。就像一个程序员面试时说”我写代码前一定先写测试”,实际干活时直接 git push。

推论:验证模型适合什么 Harness,必须是行为测量,不是访谈。


三、诊断框架:四组维度测试

把模型放到一个最简 Agent loop 里——只保留 LLM call → tool result → 循环,不加任何约束。然后分别压测四个维度。

维度一:指令遵循稳定性

模型能否精确遵守 system prompt 里的多条规则?

测试设计:system prompt 里写 5 条明确规则(如 "永远先 read 再 edit"、"每次 tool call 后必须总结")
同一 prompt 重复跑 50 轮

统计指标:
  - 规则违反率(每轮违反几条规则)
  - 规则遗忘曲线(第几轮后开始忘记最初的规则)
  - 按规则拆分的违反率(哪些规则比其他更容易被忽略)

结果解读:
  违反率 > 20% → 需要结构化 output contract + repair loop
  违反率 5-20% → 可能需要更精简的 system prompt(太长会稀释注意力)
  违反率 < 5%  → 这个维度不需要额外 Harness

维度二:结构化输出可靠性

模型能否稳定输出可解析的 JSON/XML?

测试设计:要求模型输出 100 次 JSON tool call
不 repair,直接记录每次解析结果

统计指标:
  - 原生解析成功率
  - 错误模式分布(缺少括号?字段名拼错?多余空格?)
  - repair 后覆盖率(修复后有多少能救回来)

结果解读:
  原生成功率 > 95% → 不需要额外处理
  原生成功率 80-95% → 加一个轻量 JSON repair(正则修复已知模式)
  原生成功率 < 80% → 需要完整的 structured output contract + retry loop

维度三:工具调用准确度

模型能否在正确的时候调用正确的工具,参数不出错?

测试设计:让模型完成 5 种不同类型的任务,每类 20 次
任务 mix:文件编辑、代码搜索、Shell 命令、网络请求、git 操作

统计指标:
  - 不该调用时调用了(hallucinated tool call)
  - 参数格式错误率
  - 重复错误(给 error 后是否换方法)
  - 工具选择错误(应该用 edit 却用了 write)

结果解读:
  参数错误 > 10% → 需要 tool call pre-validation layer
  重复错误 > 30% → 需要 structured error feedback
  不该调用的比例高 → 降低 tool 的描述优先级,或减少可见工具数

维度四:上下文定位能力

模型在长对话中能否准确找到关键信息?

测试设计:运行 20 轮对话后问一个第一轮提到的信息
改变轮数(10/20/30 轮)和上下文大小(4k/8k/16k tokens)

统计指标:
  - 召回正确率(是否找到关键信息)
  - 定位所需轮次(需要额外追问几次才找到)
  - 随轮数增加的衰减曲线

结果解读:
  10 轮后 recall 开始下降 → 需要上下文压缩
  20 轮后 recall 仍 > 90% → 不需要压缩(受限于 prefix cache 效率)
  定位需要 > 2 轮追问 → 需要更有效的 memory/retrieval 机制

维度五(可选):自我纠错能力

给 model 返回 error 后,下一次它是否修正了行为?

测试设计:在工具返回里注入精心构造的 error
观察模型下一次调用

统计指标:
  - 修正率(多少比例的 error 后下一次行为确实是修正的)
  - 重复错误率(相同错误连续出现几次才修正)
  - 过度修正(本来是参数问题,结果换了完全不同的策略)

结果解读:
  修正率 > 80% → 模型可靠,error message 可以精简
  修正率 < 50% → 需要系统级别的 fallback(如自动重试+参数调整)
  过度修正频繁 → error message 需要更精确地指出问题位置

四、按失败模式配置 Harness

做完五组测试,你有一张失败模式画像

模型 X 的失败模式画像:

  ✅ 指令遵循:违反率 8% → 精简 system prompt 即可
  ❌ 结构化输出:原生解析率 72% → 需要 JSON repair + structured contract
  ✅ 工具调用:参数错误率 6% → 不需要 pre-validation
  ❌ 上下文定位:12 轮后 recall 降至 60% → 需要 compaction
  ✅ 自我纠错:修正率 85% → error feedback 保持简洁

然后按画像组件选择:

失败模式推荐的 Harness 组件复杂度
指令遵循低Output contract + repair loop
结构化输出低JSON repair + fallback parser
工具调用不准Pre-validation layer + schema check
上下文定位差Auto-compaction + summary + circuit breaker
自我纠错弱Structured error format + retry orchestration

最关键的原则:不要先入为主地加所有组件。每加一个组件问:它解决了哪个失败模式?ROI 是否可测量?


五、Trajectory Slicing:让调试从 O(n) 降到 O(1)

以上框架解决了”测什么”的问题,但还有一个工程问题:怎么迭代?

传统做法:改 system prompt → 重跑完整 trajectory(20 轮)→ 看结果 → 改 → 再跑 20 轮。一次 debug 循环半小时起步。

更好的做法:Trajectory Slicing

完整 trajectory(20 轮 LLM call + 20 次 tool call)

切出第 n 步的完整输入:system prompt + 历史消息 + tool result[n-1] + tool descriptions

作为单次 chat completion 发给不同模型 / 不同 prompt 变体

只看这一步的输出变化

这个方法的好处:

  • 消去累积误差:不需要跑完 20 步才知道改 prompt 有没有效,第 1 步就知道
  • A/B 测试:同样输入给 Claude 和 DeepSeek,看 tool call 质量差异
  • Regression test:记录关键节点的输入 + 期望输出,每次改完 Harness 跑一遍
  • 定位从 O(n) 到 O(1):怀疑哪步有问题就直接拎那步出来测

要实现这个,agent loop 里每轮 persist:

{ turn_id, model, system_prompt, tool_defs, messages_so_far, tool_call_made }

然后给一个 replay 命令:输入 turn_id,不跑 agent,只跑 chat completion。


六、映射到三个产品的隐含假设

Claude Code:

  • 维度一(指令遵循):几乎没有结构化 output contract → 假设 Claude 的指令遵循足够好,XML tag 只是格式约定,不是强制
  • 维度二(结构化输出):依赖 Claude 原生 tool calling → 不 repair,不接受失败
  • 维度三(工具调用):精简工具集(read/write/edit/bash 等),不做过多的 pre-validation → 假设模型知道该调什么
  • 维度四(上下文定位):没有后端 compaction,仅靠 context window + 对话管理 → 假设 Claude 在大上下文中 recall 足够可靠

隐含前提:Claude 够强,所以 Harness 够薄。 这套策略在 Claude 上成立,但如果换到弱模型上,薄 Harness 的结果就是频繁出错。

Codex CLI:

  • 维度一:明确的审批门控 + task decomposition → 承认模型在复杂任务中需要结构性引导
  • 维度二:结构化轨迹日志 → 做了 instrumentation,但未必用来 repair
  • 维度三:Tool registry 有明确的执行策略(允许/拒绝/审批) → 控制面比 Claude Code 更丰富
  • 维度四:多 Agent 架构(orchestrator + workers)→ 通过架构而非 compaction 解决上下文限制

隐含前提:模型需要结构,但不需要怜悯。 Codex 的 Harness 比 Claude Code 更厚重,但厚重的地方是控制和隔离,不是容错。

Gemini CLI:

  • 维度一和四受到 Gemini 模型自身特性驱动——更长的 context window 和不同的 prefix caching 行为
  • 维度二和三则更紧地绑定了 Google 的生态工具

有什么共同点?三者在设计 Harness 时,都做了关于模型能力的隐含假设,只是没有说出来。这些假设决定了 Harness 的厚度、风格、和局限性。

如果把这些假设暴露出来、用测试数据验证,就能做出更理性的设计决策。


七、总结

写这篇不是要否定哪个项目。恰恰相反——两个项目都做出了有价值的东西,只是他们的设计假设不同。

核心想说的是:

  1. Harness 是杠杆,不是目标。每个组件的价值取决于它解决了哪个具体问题。

  2. 测量行为,不询问意见。模型 report 和实际 behavior 之间的 gap 是系统性的,只能通过 instrumentation 桥接。

  3. Trajectory slicing 让 debug 循环从 O(n) 降到 O(1)。这是你能否快速迭代 Harness 的工程前提。

  4. 模型升级后要重新测量。DeepSeek V3 到 V4 的结构化输出能力差了多少?如果不知道,你的 Harness 就是对着空气打拳。

最差的决策是:因为 Claude Code 用了 XML contract,所以我们也用;因为 OpenCode 用了 EventLog,所以我们也用。要对你的实际模型说话,不要对 benchmark 说话。