Agent Harness 框架深度浅出 —— 大模型落地的“操作系统“
摘要 本文揭示了AI编程助手的核心挑战——模型能力与工程落地的差距,并系统介绍了解决这一问题的"Harness驾驭框架"。文章首先指出单纯依赖ReAct模式的局限性,强调需要构建完整的基础设施来支撑AI稳定工作。随后对比了ReAct与Harness的本质区别:前者是思维模式,后者是工程操作系统。重点拆解了Harness的核心组件,包括TAOR对话循环、极简工具调度系统和设计哲学。文章通过Claud
摘要
你可能已经知道 ReAct 模式能让模型"边想边做",但真正让 AI 从"能聊天"变成"能稳定干活的编程助手"的,是包裹在模型外面的那一层基础设施——Harness(驾驭框架)。ReAct 只是其中的一块积木,Harness 才是完整的工程蓝图。本文用通俗的语言拆解它的设计思想、与 ReAct 的本质区别,以及一个生产级 Harness 到底长什么样。
一、一个尴尬的真相:模型很强,落地很脆
如果你用过任何 AI 编程助手,大概率经历过这样的过山车:
- 第一轮:惊艳。你描述了一个需求,它刷刷刷写出一段代码,逻辑清晰,注释到位。
- 第二轮:还行。你让它加个功能,它改了三个文件,有一个改错了,你手动修了一下。
- 第三轮:崩溃。你让它继续迭代,它开始瞎改之前正确的代码,删了一个你没让它删的函数,引入了一个莫名的 bug。
你很困惑:模型不是号称推理能力世界第一吗?怎么越用越拉胯?
问题不在模型,在于"生产环境"缺失。
我们来类比一下现实世界。你雇了一个非常聪明的新同事,但是:
- 没给他工位,每次来上班都要重新找位置
- 没给他权限手册,他不知道自己能动什么、不能动什么
- 没给他代码规范,他按自己的风格写,和团队完全不兼容
- 没给他验收标准,他写完就扔一边,也不跑测试
- 任务跨天时,第二天他不记得昨天做到哪了
这个新同事再聪明,产出一定是一片混乱。AI 模型就是这个新同事——它有着惊人的智力,但没有任何工程基础设施支撑它稳定工作。
所以,这篇文章要讲的 Harness,就是给模型准备的"入职培训 + 工位 + 权限卡 + 验收流程"。
二、Harness vs ReAct:别再混为一谈
很多人第一次接触 Agent 开发,就从 LangChain 的 AgentExecutor 或者 OpenAI 的 Function Calling 开始。你自然会学到 ReAct 这个词。然后你会想:Agent 不就是用 ReAct 模式让模型调工具嘛,有什么复杂的?
这是一个非常普遍的误解。我们先把它讲清楚。
2.1 ReAct对比Harness表
| 维度 | ReAct | Harness |
|---|---|---|
| 是什么 | 一种提示/执行模式(Prompt Pattern) | 一套完整的工程基础设施 |
| 核心思想 | 让模型交替输出"推理(Thought)“和"行动(Action)” | 在模型外面搭一个工程化的"操作系统" |
| 解决的问题 | 模型只会输出答案,不会主动查资料、调工具、做多步推理 | 模型能干活但不可控:权限怎么管、记忆怎么存、上下文溢出怎么办、多任务怎么协作 |
| 层级 | 模型内部认知层 | 模型外部工程层 |
| 包含什么 | 一个循环:Thought → Action → Observation → Thought → … | 六个子系统:对话循环 + 工具调度 + 权限管线 + 上下文管理 + 记忆系统 + 子 Agent 编排 |
| 类比 | 教一个人"遇到问题先想、再做、再看结果、再调整"的思维习惯 | 给这个人工位、工具、权限卡、公司制度、验收标准、同事协作机制 |
2.2 ReAct 到底做了什么?
ReAct 论文的核心思路很简单:传统语言模型回答问题,是"一次性思考然后输出"。但如果问题需要查询外部信息或多步推理呢?ReAct 的做法是:让模型交替输出"思考"和"行动"。
举个例子,模型回答"2025 年苹果公司 CEO 是谁?":
Thought: 我需要查一下苹果公司现任 CEO 是谁。
Action: Search[Apple CEO 2025]
Observation: Tim Cook 自 2011 年起担任苹果 CEO,至今仍在任。
Thought: 搜索结果确认 Tim Cook 是苹果 CEO。
Answer: 2025 年苹果公司的 CEO 是 Tim Cook。
这就是 ReAct 的全部核心。它解决了一个真实问题——在此之前,模型不会主动查资料。
2.3 那 Harness 在 ReAct 之外加了什么?
ReAct 定义了循环内部的交互模式。Harness 在这个循环的外面,叠了一层又一层的工程保障。
我们用一张图来表示它们的关系:
这张图说明了一个关键事实:ReAct 是 Harness 心脏里的一根神经,但它离一个能上生产的 Agent 还隔着五六层工程系统。
2.4 一个血泪教训:LangChain 踩过的坑
2023 年,LangChain 几乎把"Agent"和"ReAct"画了等号。它的 AgentExecutor 本质上就是一个 ReAct 循环:模型输出 Thought-Action,框架解析 Action 并执行工具,把结果塞回去,继续循环。
它的问题是:只考虑了"怎么做",没考虑"怎么不翻车"。
- 上下文爆炸:每轮调用的工具输出都往上下文里塞,第 10 轮就超出窗口了,模型开始"幻觉",输出胡言乱语
- 权限裸奔:模型想执行
rm -rf /?AgentExecutor不会有任何阻拦 - 没有记忆:第二次对话从零开始,完全不记得上次做过什么
- 无错误恢复:工具调用失败了?抛个异常,Agent 直接挂掉
这些问题,LangChain 不是不想解决,而是它的架构从一开始就没给这些子系统留位置。ReAct 循环就是它的一切。
现在来看 Claude Code 是怎么做的。
Claude Code 的对话循环(他们叫 TAOR:Think → Act → Observe → Repeat)本质上也受了 ReAct 启发,但它的核心设计哲学完全不同:
“把智能下放给模型,把确定性留给框架。”
翻译成人话:循环本身写得极简——就一个 while(true),所有推理交给模型。但循环外面,叠了六层基础设施。真正值钱的不是那个循环,是循环外面的东西。
这就是为什么我说 LangChain 把 ReAct 当成了终点,Claude Code 把 TAOR 当成了起点。
三、心脏:TAOR 对话循环
理解了 Harness 的全貌和它与 ReAct 的关系,现在我们来拆解每个子系统。
先看最核心的 TAOR 循环。它是整个 Agent 的"心跳"。
3.1 循环就是这几行
剥离掉所有工程细节,TAOR 循环伪代码长这样:
// TAOR 对话循环 —— "心脏"
async function agentLoop(messages: Message[]): Promise<string> {
while (true) {
// 1. Think:模型推理
const response = await model.generate(messages);
// 2. 如果模型直接返回文本(无工具调用),终止循环
if (!response.hasToolCalls()) {
return response.text;
}
// 3. Act + Observe:执行所有工具调用,收集结果
const toolResults = [];
for (const toolCall of response.toolCalls) {
const result = await executeTool(toolCall);
toolResults.push(result);
}
// 4. Repeat:把结果塞回消息列表,下一轮继续
messages.push(response);
messages.push(...toolResults);
}
}
就这些。不到 30 行。没有策略模式,没有状态机,没有复杂的调度器。
3.2 为什么要设计得这么"笨"?
这里的"笨"不是贬义,而是一种刻意的工程设计决策。
早期框架(最典型的是 LangChain)试图把编排逻辑也写进框架:什么情况下重试、什么情况下换工具、什么情况下给模型发警告、什么情况下提前终止。结果呢?
- 框架越来越臃肿
- 框架的"智能决策"和模型的"智能推理"互相干扰
- 模型升级后,框架的硬编码逻辑反而成了短板
Anthropic 的反向思路是:
“运行时越笨,系统越稳。所有智能都在模型那一侧。”
这个设计很妙。因为模型会持续升级——Claude 3.5 到 4.5 到 4.8——每次升级,它的推理能力都在变强。如果你的循环里写死了"什么时候该重试"这种判断,模型升级后你的判断逻辑可能就过时了。但如果循环本身什么判断都不做,只负责"模型说了什么就执行什么",那么模型一升级,系统自动受益。
循环的复杂度不增长,智能的上限随模型自动提升。
四、骨骼:工具调度系统
有了心跳,Agent 还需要"手脚"来干活。
4.1 极简工具哲学:只给四个原语
很多框架一上来就给你几十种工具:ReadFile、WriteFile、SearchCode、RunTest、DeployAws、QueryDatabase…
Claude Code 的做法是:只给四个最基础的原子操作,其余全通过组合完成。
| 原语 | 能力 | 为什么够用 |
|---|---|---|
| Read | 读取文件 | 代码审查、理解项目 |
| Write | 写入/修改文件 | 写代码、改配置 |
| Execute | 执行 shell 命令 | 跑测试、安装依赖、git 操作、调 API… |
| Connect | 通过 MCP 连接外部服务 | 数据库、第三方 API、CI/CD |
Execute 是"万能适配器"。跑测试是 npm test,格式检查是 npx prettier --check .,git 提交是 git commit -m "...",甚至调 GitHub API 都可以用 gh pr list。模型本来就会 shell 命令,不需要你给它包一层又一层的语义封装。
这种设计还有一个隐藏的好处:减少工具的维护负担。每多一个专用工具,就多一个需要维护的接口、多一个可能出 bug 的地方。四个原子操作覆盖一切,维护成本降到最低。
4.2 工具执行管线:从调用到返回的 8 步
但"极简"不等于"简陋"。一次工具调用在 Harness 里要走完整条管线:
模型输出 tool_call
→ ① 查找工具定义(支持 50+ 个工具,但按需懒加载)
→ ② Zod 参数校验(类型不对直接拒绝,不给模型试错的机会)
→ ③ 并发标记检查(只读工具标记为可并行,写入工具标记为必须串行)
→ ④ 权限预检(调用权限管线,详见第五章)
→ ⑤ 并行调度(只读工具一起跑,写入工具排队跑)
→ ⑥ 流式执行 + 超时保护
→ ⑦ 结果裁剪(输出太长?保留头尾 + 中间摘要)
→ ⑧ 后置 Hook(如 lint 检查、格式化验证)
→ 结果塞回消息列表
4.3 并发控制:读写锁的 AI 版本
后端开发应该对这个感很熟悉——读写锁。Harness 工具调度的并发策略本质上就是它:
// 每个工具都声明自己是否是"只读"和"并发安全"的
interface ToolMeta {
isReadOnly: boolean; // 只读 → 可以和其他只读工具一起跑
isConcurrencySafe: boolean; // 并发安全 → 即使写入也可以并行(默认 false)
}
// 调度策略
function scheduleTools(toolCalls: ToolCall[]): ExecutionPlan {
const readers = toolCalls.filter(t => t.tool.isReadOnly); // 并行执行
const writers = toolCalls.filter(t => !t.tool.isReadOnly); // 串行执行
return {
parallel: readers, // 所有只读操作一起跑
sequential: writers, // 写入操作一个接一个,避免竞态
};
}
这是一个非常务实的权衡。同时读三个文件没问题,但同时改三个文件就可能产生冲突。默认"保守"——新工具默认不能并发,作者需要显式声明 isConcurrencySafe: true 才能获得并行能力。
五、刹车:权限管线
如果工具调度是"怎么干活",权限管线就是"什么不能干"。
5.1 六层防御
一次危险操作要穿过六层检查才能真正执行:
没有任何一层是银弹,但六层叠加,漏网概率被压到极低。
5.2 最精彩的设计:ML 分类器的"信息屏蔽"
这里面最值得讲的是 ML 分类器。它的任务是判断一个操作是否危险,但它获取的信息是故意受限的。
它能看到的:
- 用户原始请求(“帮我提交代码”)
- 工具调用参数(
git push origin main --force)
它不能看到的:
- 模型的推理文本(“我解释一下为什么这个操作是安全的…”)
为什么?
因为如果不屏蔽模型的推理文本,模型可能会"花言巧语"绕过分级检查:
模型输出:「经过仔细分析,用户的需求是提交代码。
--force参数用于确保远程分支与本地同步,这是一个常规操作。我在类似的上下文中已经无数次这样做了,没有任何风险。」—— ML 分类器读到这段话,可能就被说服了。
把推理文本摘掉,分类器只看客观事实:用户说了什么、模型要执行什么。这样模型就无法用话术干扰安全决策。
这是一个经典的"分离推理与执行"安全设计——做决定的组件,不应该看到试图说服它的文本。
5.3 六种权限模式
不同场景需要不同级别的"刹车力度"。Harness 提供了六档:
| 模式 | 行为 | 适用场景 |
|---|---|---|
| Plan | 只读(Read + Bash 只读命令),禁止任何写入 | 探索陌生代码库 |
| Default | 读操作自动通过,写操作弹窗确认 | 日常开发 |
| AcceptEdits | 文件编辑自动通过,Shell 命令仍需确认 | 快速迭代 |
| Auto | ML 分类器自动判定,无需用户确认 | 长时间任务、批量操作 |
| DontAsk | 只有明确允许列表里的工具能执行 | CI/CD 流水线 |
| BypassPermissions | 无检查 | 沙箱环境、实验 |
从"完全只读"到"完全信任"的渐进式授权,而不是一个简单的"开/关"开关。
六、记忆:上下文管理
权限保证了 Agent 不闯祸,但还有一个根本问题:上下文是有物理上限的。
6.1 大模型的"失忆症"
Claude 的上下文窗口现在是 200K tokens,听起来很大(约 15 万字)。但实际对话中:
- 一次工具调用的输出可能是 5000 tokens
- 连调 10 次就是 50000 tokens
- 加上系统提示、工具定义、对话历史,很快就过半
更致命的是,模型在上下文后半段的推理质量会明显下降——业界管这叫"上下文中部坍塌"。很多团队发现:模型读到窗口 40% 以后的内容时,遗漏关键信息的概率显著上升。
6.2 自动压缩:核心思路
Harness 的解决方案叫自动压缩——当上下文接近危险阈值时,用一个轻量模型(或同一个模型但用更短的提示)对之前的对话做摘要:
压缩前(原始对话): 压缩后(摘要):
"好的,我来读取 auth.ts 文件..." [已读取 auth.ts——这是一个 230 行的认证中间件,
"我找到了 authenticate 函数..." 使用 JWT + 拦截器模式,入口在 login() 函数]
"这个函数使用 JWT..."
"中间件在 /src/middleware/..."
压缩会保留:
- ✅ 架构决策(“我们选择用 Redis 做会话缓存”)
- ✅ 未解决的 bug(“
login()的超时处理还没写”) - ✅ 任务进度(“已完成用户模块,正在做支付模块”)
压缩会丢弃:
- ❌ 冗余的工具输出(读同一个文件三次?只保留最后一次)
- ❌ 已解决的问题(“变名命名已从
foo改为userHandler”——已知,不再重复) - ❌ 模型的客套话和过程描述
6.3 熔断机制
还有一个精巧的设计:连续压缩熔断。
如果 Harness 连续 3 次尝试压缩上下文都失败了(比如模型一直输出新内容,压缩跟不上),它就不再尝试压缩,改为直接截断最旧的消息。
为什么要这样?因为如果没有熔断,可能会陷入一个死循环:
上下文快满了 → 压缩 → 压缩产生了更多 tokens → 更满了 → 再压缩 → ...
每次压缩调用 API 都要花钱。真实数据是:这个 3 次熔断机制每天为 Anthropic 节省了约 25 万次无效 API 调用。
6.4 三层加载策略
不是所有信息都应该在每一轮对话中都塞进上下文。Harness 采用分层加载:
┌─────────────────────────────────────┐
│ 第一层: CLAUDE.md(每轮必读) │ ← 项目约定、编码规范、常用命令
│ 第二层: Memory(跨会话学习) │ ← "上次用户说这里不应该用 ORM,用原生 SQL"
│ 第三层: Skills(按需懒加载) │ ← "用户在做数据库迁移?加载迁移 Skill"
└─────────────────────────────────────┘
核心原则:能用文件存储的就不占上下文,能从文件推导的就不写入记忆。
七、协作:子 Agent 编排
当任务变复杂,单个 Agent 的上下文和注意力都会成为瓶颈。解决方案是多 Agent 协作。
7.1 上下文防火墙
主 Agent 可以派生子 Agent 去执行子任务。关键设计是:子 Agent 有独立的上下文空间。
主 Agent(上下文 100K tokens)
│
├── 派生子 Agent A:「审计 /src/auth/ 下所有文件的安全漏洞」
│ └── Agent A 独立上下文(30K tokens) → 返回:「发现 2 个漏洞:(1)... (2)...」
│
├── 派生子 Agent B:「审计 /src/payment/ 下所有文件的安全漏洞」
│ └── Agent B 独立上下文(25K tokens) → 返回:「发现 1 个漏洞:(1)...」
│
└── 主 Agent 拿到两份摘要,合成最终报告
主 Agent 只拿到结论,不需要看到中间过程。这就叫"上下文防火墙"——子 Agent 内部的思考、试错、冗余操作全部被隔在里面,不污染主 Agent 的上下文。
7.2 四种内置角色
Claude Code 内置了四种专用子 Agent:
| Agent 类型 | 权限 | 用途 | 类比 |
|---|---|---|---|
| Explore | 只读搜索 | 代码库发现、找文件、了解结构 | 侦察兵 |
| Plan | 只读分析 | 设计方案、评估影响、技术调研 | 军师 |
| Verify | 写入 /tmp + 运行测试 | 对抗性验证——“试试能不能搞崩这个实现” | 质检员 |
| General | 完整工具权限 | 通用开发任务,任何不特化的工作 | 全栈工程师 |
这里最值得关注的是 Verify Agent。它的任务不是"再读一遍代码",而是尝试破坏:
- 传入边界值看会不会崩溃
- 并行调用看有没有竞态
- 故意断开网络看超时处理是否健壮
这是一种对抗性验证——让一个 Agent 写代码,另一个 Agent 想办法找出它的漏洞。你永远不应该让写完代码的 Agent 自己检查自己——谁检查自己写的代码都是带着偏见的。
7.3 编排模式
当你有多个 Agent 同时工作,就需要编排。最常见的三种模式:
循环收敛模式特别适合代码审计类任务:一直找 bug,直到连续两轮找不到新问题才停止。这比"跑一次就完"要可靠得多。
八、接口:MCP 协议
一个 Agent 框架要真正有用,必须能连接各种外部服务。MCP(Model Context Protocol,模型上下文协议)就是解决这个问题的。
8.1 没有 MCP 的世界
在没有标准协议之前,每家 Agent 框架都各写各的工具集成:
┌───────┐
│ Agent │
└──┬────┘
┌───────────┼───────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│GitHub │ │ DB │ │ Slack │
│ 专用 │ │ 专用 │ │ 专用 │
│ 适配 │ │ 适配 │ │ 适配 │
└───────┘ └───────┘ └───────┘
每个外部服务都要写一套专属的适配代码。GitHub 写一套,GitLab 再写一套,Jira 还要写一套。这是典型的 N×M 爆炸问题。
8.2 MCP 的解法
MCP 做的事很简单:定义一个通用接口,任何人实现这个接口,任何 Agent 就能用。
┌────────────┐
│ Agent │
│ (MCP Client)│
└─────┬──────┘
│ MCP 标准协议
┌─────────────┼─────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ GitHub │ │ DB │ │ Slack │
│ Server │ │ Server │ │ Server │
└────────┘ └────────┘ └────────┘
MCP Server 的实现非常简单——封装成一个小程序,定义自己的工具列表(名字 + 参数 + 描述),Agent 连接你时自动发现所有可用工具。如果你用过 USB 设备,应该立刻能理解这个设计:USB 接口是标准,设备厂商各做各的实现,电脑插上就能用。MCP 就是 Agent 世界的 USB 标准。
这个协议已经被 Anthropic 捐赠给了 Linux 基金会,成为一个开放标准。这意味着未来会有越来越多的服务原生支持 MCP,Agent 能即插即用的工具生态只会越来越大。
九、结束语:模型会被淘汰,Harness 不会
这篇文章讲了 Harness 的六个子系统:
- TAOR 对话循环(心脏):最简单的
while(true),所有智能在模型侧 - 工具调度(手脚):四个原子原语覆盖一切,读写分离并发调度
- 权限管线(刹车):六层防御 + ML 分类器信息屏蔽
- 上下文管理(记忆):自动压缩 + 分层加载 + 熔断保护
- 子 Agent 编排(协作):上下文防火墙 + 对抗性验证
- MCP 协议(接口):Agent 世界的 USB-C
这六个子系统定义了一套长期有效的工程范式。模型会迭代——GPT-5、GPT-6、Claude 5、Claude 6——但 Harness 的设计思想不会过时。因为不管你换什么模型,它都需要权限控制、上下文管理、工具调度、多 Agent 协作。
如果你想亲身体验一个生产级 Harness,Claude Code 是一个很好的起点——它就是这篇文章拆解的那套系统,你装好就能用。你要做的不是从零搭建 Harness,而是给它写好 “CLAUDE.md”——你的项目说明、编码规范、质量门禁。
Harness 的一个核心信条是:当 AI 犯错时,不要只修代码,要加一条规则防止它再犯。
同样的模型,加上更好的 Harness,表现可以天差地别——有真实数据表明,仅通过规范化 CLAUDE.md,同一个模型的任务完成率从 52.8% 提升到了 66.5%。
模型是易耗品。Harness 才是你积累下来的资产。
更多推荐

所有评论(0)