摘要

你可能已经知道 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 在这个循环的外面,叠了一层又一层的工程保障。

我们用一张图来表示它们的关系:

Harness(驾驭框架)

权限管线 —— 安全刹车

工具调度 —— 手脚协调

TAOR 对话循环 —— 心脏

← ReAct 模式在这里 →

上下文管理 —— 记忆防丢失

记忆系统 —— 跨会话学习

子 Agent 编排 —— 团队协作

MCP 协议 —— 即插即用的 USB

这张图说明了一个关键事实: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 极简工具哲学:只给四个原语

很多框架一上来就给你几十种工具:ReadFileWriteFileSearchCodeRunTestDeployAwsQueryDatabase

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 六层防御

一次危险操作要穿过六层检查才能真正执行:

模型发出 rm -rf /

① 全局黑名单
硬编码禁止

② 目录隔离
锁定到项目目录

③ 只读检测
写操作触发审批

④ 安全模式匹配
已知危险模式直接拦截

⑤ ML 分类器
AI 判定风险等级

⑥ 用户确认
弹窗等用户决策

执行 / 拒绝

没有任何一层是银弹,但六层叠加,漏网概率被压到极低。

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 同时工作,就需要编排。最常见的三种模式:

循环收敛模式

Agent: 找 Bug

有新 Bug?

Agent: 验证 Bug

结束:连续2轮无新发现

并行模式

Agent A
审计模块1

Agent B
审计模块2

Agent C
审计模块3

流水线模式

Agent A
代码生成

Agent B
代码审查

Agent C
测试验证

循环收敛模式特别适合代码审计类任务:一直找 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 的六个子系统:

  1. TAOR 对话循环(心脏):最简单的 while(true),所有智能在模型侧
  2. 工具调度(手脚):四个原子原语覆盖一切,读写分离并发调度
  3. 权限管线(刹车):六层防御 + ML 分类器信息屏蔽
  4. 上下文管理(记忆):自动压缩 + 分层加载 + 熔断保护
  5. 子 Agent 编排(协作):上下文防火墙 + 对抗性验证
  6. 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 才是你积累下来的资产。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐