Harness 解剖学

Harness 解剖学

原文:The Anatomy of an Agent Harness
本文根据原文提炼核心观点
「如果你不是模型,那你就是 harness。」
Beren Millidge 在他 2023 年的文章《Scaffolded LLMs as Natural Language Computers》里把这个类比讲得非常清楚。 一个原始的 LLM 就是一颗没有 RAM、没有硬盘、没有 I/O 的 CPU。 上下文窗口扮演 RAM 的角色(快但小);外部数据库相当于硬盘(大但慢);工具集成就是设备驱动;harness 则是操作系统。Millidge 写道:「我们重新发明了冯·诺依曼架构」——因为对任何一个计算系统来说,这都是最自然的抽象。

三个层次的工程

  • Prompt 工程 :设计模型收到的指令。
  • Context 工程 :管理模型在什么时候看到什么。
  • Harness 工程 :把上面两层全包进来,再加上整个应用层的基础设施——工具编排、状态持久化、错误恢复、验证循环、安全执行、生命周期管理。

harness 不是一个 prompt 外面的包装层。它是让自治 agent 行为成为可能的那一整套系统。

生产级 Harness 的 12 个组件

一、循环编排

它实现的是 Thought-Action-Observation(TAO)循环,也叫 ReAct 循环。整个流程是: 组装 prompt,调 LLM,解析输出,执行工具调用,把结果喂回去,重复直到结束。
机械层面看,它往往就是一个 while 循环。 复杂度不在循环本身,而在循环管理的所有东西里。 Anthropic 把自己的 runtime 形容为一个「dumb loop」——所有智能都在模型里,harness 只负责管理每一轮。

二、工具

工具是 agent 的手,它们以 schema 的形式(包括名字、描述、参数类型)注入到 LLM 的上下文中,让模型知道自己有哪些手段可以使用。工具层负责注册、schema 验证、参数提取、沙箱执行、结果捕获,以及把结果格式化成 LLM 能读懂的 Observation。

Claude code 提供了 6 类工具:

  • 文件操作
  • 搜索
  • 执行
  • web访问
  • code intelligence
  • subagent 派生

OpenAI 提供了三类工具:

  • 函数工具(通过@function_tool
  • 托管工具(WebSearch、CodeInterpreter、FileSearch)
  • MCP 工具

三、记忆

短期记忆是单次会话的对话历史。长期记忆是跨会话的持久记忆。

Claude Code 使用三层结构:

  • 一个轻量级的索引,永远加载 (唐biubiu:这里说的应该是CLAUDE.md
  • 按需拉取的详细主题文件 (唐biubiu:这里说的应该是MEMORY.md
  • 只能通过搜索访问的原始 transcript。

OpenAI 则用基于 SQLite 或 Redis 的 Sessions。
LangGraph 用的是按命名空间组织的 JSON Store

四、上下文管理

这是很多 agent 静默失败的地方。核心问题是 context rot(上下文腐烂):关键内容一旦落在窗口中段,模型表现会下降 30% 以上 (Chroma 的研究,和斯坦福的「Lost in the Middle」结论互相印证)。哪怕是百万 token 的窗口,上下文一变长,指令遵循能力也会下降。

生产环境常见的策略有;

  • 压缩(Compaction) :在接近上限时对对话历史做总结(Claude Code 会保留架构决策和尚未解决的 bug,同时丢掉冗余的工具输出)。
  • Observation 屏蔽 :JetBrains 的 Junie 隐藏掉旧的工具输出,但保留工具调用本身可见。
  • 即时检索(JIT) :只保留轻量级标识符,动态加载数据(Claude Code 用 grep、glob、head、tail,而不是一次性加载整个文件)。
  • 子 agent 委派 :每个 subagent 自己大量探索,但只返回 1,000 到 2,000 token 的浓缩总结。

Anthropic 的 context 工程指南把目标说得很清楚: 找到最小的一组高信号 token,把达到期望结果的概率最大化。

五、Prompt 构造

这一步把模型每一步真正看到的内容组装起来。它是分层的:

  • system prompt
  • 工具定义
  • 记忆文件
  • 对话历史
  • 当前用户消息

CodeX 采用严格的优先级:

  • 服务端控制的 system message(最高优先级)
  • 工具定义
  • 开发者指令
  • 用户指令(以及AGENTS.md 文件,32 KiB 上限)
  • 对话历史

六、输出解析

现代 harness 依赖原生工具调用——模型直接返回结构化的 tool_calls 对象,而不是需要解析的自由文本。harness 只需要检查:有工具调用吗?有就执行并继续循环。没有?那就是最终答案。
对结构化输出,OpenAI 和 LangChain 都通过 Pydantic 模型支持 schema 约束的响应。老式方法如 RetryWithErrorOutputParser(把原 prompt、失败的补全、解析错误一起喂回模型)在边缘场景下仍然可用。

七、状态管理

LangGraph 把状态建模成在图节点之间流动的类型化字典,用 reducer 合并更新。checkpoint 发生在 super-step 边界,支持中断后恢复和时间回溯调试。
OpenAI 给出四种互斥策略:application memory、SDK sessions、服务端 Conversations API、或者轻量的 previous_response_id 串联。
Claude Code 选了另一条路: git commit 当 checkpoint,进度文件当结构化草稿纸。

八、错误处理

这件事为什么重要? 一个 10 步流程,哪怕每一步成功率都是 99%,端到端成功率也只有 ~90.4%。 错误复合得非常快。

LangGraph 把错误分成四类:

  • 瞬时错误(按退避策略重试)
  • LLM 可恢复错误(把错误作为 ToolMessage 返回,让模型自己调整)
  • 用户可修复错误(中断等待人类输入)
  • 意外错误(向上冒泡用于调试)

Anthropic 在工具 handler 里捕获所有失败,作为 error result 返回来保证循环继续。

Stripe 的生产 harness 把重试次数上限设为 2 次。

九、护栏与安全

OpenAI 提供三级护栏:

  • 输入护栏(在第一个 agent 上执行)
  • 输出护栏(在最终输出上执行)
  • 工具护栏(在每次工具调用时执行)
    一旦「tripwire」机制被触发,agent 立刻停机。

Anthropic 在架构层面把权限执行和模型推理分开。 模型决定尝试做什么,工具系统决定什么被允许。 Claude Code 对大约 40 个离散的工具能力分别设卡,分三个阶段:

  • 项目加载时建立信任
  • 每次工具调用前做权限检查
  • 对高风险操作要求用户显式确认。

十、循环验证

这是把玩具 demo 和生产级 agent 真正分开的一条线。

Anthropic 推荐三种路径:

  • 基于规则的反馈(测试、linter、类型检查)
  • 视觉反馈(UI 任务用 Playwright 截图)
  • LLM-as-judge(专门的 subagent 评估输出)。
    Claude Code 的作者 Boris Cherny 指出: 给模型一个自我验证的办法,质量能提升 2 到 3 倍。

十一、sub-agent编排

Claude Code 支持三种执行模型:

  • Fork (父上下文的字节级副本)
  • Teammate (独立的终端 pane,通过基于文件的邮箱通信)
  • Worktree (每个 agent 拥有自己的 git worktree 和隔离分支)。

OpenAI 的 SDK 支持 agents-as-tools(专家处理受限子任务)和 handoffs(专家接管全部控制权)
LangGraph 把 subagent 实现为嵌套的状态图。

循环运转:一步一步走一遍

知道了组件之后,我们跟一遍它们在一次循环里是怎么协作的。


第 1 步(Prompt 组装) :harness 构造完整输入——system prompt + 工具 schema + 记忆文件 + 对话历史 + 当前用户消息。重要内容放在 prompt 的开头和结尾(「Lost in the Middle」的发现)。
第 2 步(LLM 推理) :组装好的 prompt 发给模型 API。模型生成输出 token:文本、工具调用请求,或两者兼有。
第 3 步(输出分类) :如果模型只生成了文本、没有工具调用,循环结束。如果请求了工具调用,进入执行阶段。如果请求了 handoff,更新当前 agent 并重启循环。
第 4 步(工具执行) :对每个工具调用,harness 校验参数、检查权限、在沙箱里执行、抓取结果。 只读操作可以并发执行;写操作串行执行。
第 5 步(结果打包) :工具结果被格式化成 LLM 能读懂的消息。错误会被捕获并作为 error result 返回,让模型自行纠正。
第 6 步(上下文更新) :结果被追加到对话历史。如果接近上下文窗口上限,harness 触发压缩。
第 7 步(循环) :回到第 1 步,直到终止。

终止条件是分层的:模型生成一条没有工具调用的响应、达到最大轮数、token 预算耗尽、护栏 tripwire 被触发、用户中断、或者返回安全拒绝。一个简单问题可能只需要 1 到 2 轮。一个复杂的重构任务可能横跨许多轮、串起几十次工具调用。

对于跨越多个上下文窗口的长任务,Anthropic 设计了一个两阶段的「Ralph Loop」模式:Initializer Agent 先搭好环境(init 脚本、进度文件、特性列表、初始 git commit),之后每一次会话里的 Coding Agent 都通过读取 git log 和进度文件来自我定位,挑出最高优先级的未完成特性,动手、提交、写总结。 文件系统充当了跨上下文窗口的连续性载体。

真实框架是怎么实现这套模式的

Anthropic 的 Claude Agent SDK 通过一个 query() 函数暴露 harness,它创建 agent 循环并返回一个流式推送消息的异步迭代器。runtime 是一个「dumb loop」,所有智能都在模型里。Claude Code 使用的是 Gather-Act-Verify 循环 :收集上下文(搜文件、读代码)→ 采取行动(改文件、跑命令)→ 验证结果(跑测试、看输出)→ 重复。

OpenAI 的 Agents SDK 通过 Runner 类实现 harness,提供三种模式:async、sync、streamed。这个 SDK 是「code-first」的——工作流逻辑用原生 Python 表达,而不是图 DSL。Codex harness 在此基础上扩展成三层架构: Codex Core (agent 代码 + runtime)、 App Server (双向 JSON-RPC API)、 client 层 (CLI、VS Code、web app)。所有客户端共享同一个 harness,这也是为什么「Codex 模型在 Codex 客户端上的体感比在通用聊天窗口里更好」。

LangGraph 把 harness 建模成一张显式的状态图。两个节点(llm_call 和 tool_node)之间用一条条件边连接:有工具调用就走 tool_node,没有就走 END。LangGraph 是从 LangChain 的 AgentExecutor 演化来的——后者在 v0.2 被废弃,原因是难扩展且不支持多 agent。LangChain 的 Deep Agents 明确使用「agent harness」这个术语:内置工具、规划(write_todos 工具)、用于上下文管理的文件系统、subagent 派生、持久化记忆。

CrewAI 采用的是基于角色的多 agent 架构:Agent(LLM 外面的 harness,由 role、goal、backstory 和 tools 定义)、Task(工作单位)、Crew(agent 集合)。CrewAI 的 Flows 层在此之上加了一条「确定性骨干 + 局部智能」:Flows 负责路由和验证,Crew 负责自治协作。

AutoGen (正在演化为 Microsoft Agent Framework)首创了「对话驱动」的编排方式。它的三层架构(Core、AgentChat、Extensions)支持五种编排模式:顺序、并发(fan-out/fan-in)、群聊、handoff、magentic(一个 manager agent 维护一份动态任务 ledger 来协调各专家)。

定义每个 Harness 的七个关键决策

  1. 单 agent vs. 多 agent。 Anthropic 和 OpenAI 都给出同一个建议:先把单 agent 的能力榨到极致。多 agent 系统会引入额外开销(路由的额外 LLM 调用、handoff 过程中的上下文损失)。只有当工具数超过 ~10 个且互相重叠,或者任务域明显可分时,才考虑拆分。
  2. ReAct vs. plan-and-execute。 ReAct 在每一步都把推理和行动交织起来(灵活但每步成本更高)。plan-and-execute 把规划和执行拆开。 LLMCompiler 报告相比顺序 ReAct 提速 3.6 倍。
  3. 上下文窗口管理策略。 生产级常见的五种方法:基于时间的清理、对话摘要、observation 屏蔽、结构化笔记、子 agent 委派。 ACON 的研究表明:优先保留推理轨迹、丢掉原始工具输出,能减少 26% 到 54% 的 token,同时保持 95% 以上的准确率。
  4. 验证循环设计。 计算型验证(测试、linter)提供确定性的 ground truth。推断型验证(LLM-as-judge)能捕获语义问题但引入延迟。Martin Fowler 在 Thoughtworks 团队把这件事总结成 guides(前馈,行动前引导)和 sensors(反馈,行动后观察)两类。
  5. 权限与安全架构。 宽松型(快但有风险,大多数操作自动通过)vs. 严格型(安全但慢,每个操作都要批准)。怎么选,取决于部署场景。
  6. 工具边界策略。 工具越多往往表现越差。 Vercel 从 v0 里砍掉了 80% 的工具,反而跑出了更好的结果。 Claude Code 通过懒加载做到 95% 的上下文缩减。原则是:只暴露当前这一步真正需要的最小工具集。
  7. Harness 厚度。 有多少逻辑放在 harness 里,有多少放在模型里。 Anthropic 押注薄 harness 加模型进化。 基于图的框架则押注显式控制。Anthropic 每次新模型把规划能力内化之后,都会从 Claude Code 的 harness 里删掉对应的规划步骤。