从 LangChain 到 Harness:Coding Agent 的架构演进之路


导语

2024 年底到 2025 年初,Coding Agent 赛道迎来了一次范式跳跃。

从最早的 LangChain 式”显式编排”,到 AutoGPT 式”自主循环”,再到如今以 Claude Code 为代表的 Harness 范式——AI 编程助手的架构设计在短短两年内经历了三次根本性的转变。

昨晚 Claude Code 的源码意外泄露,我第一时间通读了其完整架构,试图回答一个核心问题:为什么同样是”让 LLM 写代码”,不同架构的体验差距如此之大?

这篇文章不是一篇源码导读,而是我对 Coding Agent 架构演进的思考总结。我会从以下几个维度展开:

  1. 🔄 Agent 架构的三次范式转变
  2. 🏗️ Harness 范式的工程设计哲学
  3. 🧠 如何让 Agent 不丢失上下文
  4. 🎯 如何让模型减少幻觉
  5. 🔧 工具系统的精妙设计
  6. ⚡ Edge Case 的工程化处理

一、Agent 架构的三次范式转变

1.1 第一代:显式编排时代(LangChain / LangGraph)

2023 年,LangChain 几乎是所有 LLM 应用的标准起点。它的核心思想是显式编排:开发者用 Chain、Pipe、DAG 把 LLM 的调用流程”画”出来。

1
2
3
4
5
6
7
8
# LangChain 的典型模式:开发者预定义工作流
chain = (
prompt_template
| llm
| output_parser
| tool_selector
| tool_executor
)

这个模式的问题在于:开发者必须预先知道所有可能的执行路径。每当你想支持一个新场景,就必须回去修改 chain 定义。这在面对代码编辑这种高度动态的场景时,几乎不可能穷举。

更致命的是状态管理。LangChain 把状态管理的责任完全交给了开发者——你需要手动管理对话历史、手动处理上下文窗口溢出、手动定义错误恢复路径。当你的 Agent 需要在一个 10 万行的项目里做跨文件重构时,这种手动管理几乎是不可能完成的任务。

1.2 第二代:自主循环时代(AutoGPT / BabyAGI)

2023 年中,AutoGPT 的出现让所有人兴奋了一阵子。它的思路很简单:让 LLM 自己决定下一步做什么,循环执行直到任务完成

1
2
3
4
5
while not done:
thought = llm.think(context)
action = llm.decide_action(thought)
result = execute(action)
context.append(result)

这个方向是对的,但当时的执行有严重缺陷:

  • 上下文快速爆炸:没有压缩策略,几轮循环就塞满了上下文窗口
  • 幻觉失控:缺乏环境反馈的 grounding,LLM 经常在想象中”完成”了任务
  • 没有安全边界rm -rf / 说执行就执行,没有任何防护
  • 工具原始:只有最基本的文件读写和 shell 执行

AutoGPT 的方向正确但时机不对——当时的模型能力不足以支撑自主决策,而工程基础设施也远远不够。

1.3 第三代:Harness 范式(Claude Code / Cursor / Codex CLI)

到了 2025 年,一种新的范式逐渐成型,我称之为 Harness 范式。它的核心理念用一句话概括就是:

不要编排 LLM,要为 LLM 构建一个丰富的运行时环境,然后让它自己决定怎么做。

“Harness” 这个词很精妙——它既有”驾驭、利用”的意思,也有”安全带、保护装置”的含义。一个好的 Harness 既要赋能(给 LLM 足够的工具和信息),又要约束(确保安全、防止失控)。

Claude Code 是这个范式目前最极致的实现。它的 Agent Loop 核心逻辑简洁得令人惊叹:

1
2
3
4
5
6
7
8
while (模型未停止):
response = callAPI(messages)
if response 包含 tool_use:
results = executeTools(tool_calls)
messages.append(results)
continue // 模型自己决定下一步
else:
break // 模型认为任务完成

就这么几行伪代码,支撑起了一个能做跨文件重构、多代理协作、无限对话的编程助手。所有的”智能”不是来自复杂的编排逻辑,而是来自三个方面:

  1. 模型本身的推理能力
  2. 精心设计的系统提示
  3. 丰富且高质量的工具反馈

1.4 三代对比

维度 显式编排 (LangChain) 自主循环 (AutoGPT) Harness (Claude Code)
控制流 开发者定义 DAG LLM 自主但粗糙 LLM 自主 + 精密环境
状态管理 手动管理 几乎没有 多层自动管理
安全性 开发者负责 几乎没有 多层权限 + 沙箱
扩展性 改 chain 定义 改代码 加工具/MCP 服务器
上下文 固定窗口 快速溢出 多层压缩 + 无限对话
核心瓶颈 开发者的想象力 模型的自控力 模型的推理能力

这个演进的本质是:随着模型能力的提升,控制权从开发者逐渐转移到模型本身。LangChain 时代,开发者不信任模型,所以要用 DAG 来”管”它;Harness 时代,模型已经足够强,开发者要做的是构建好环境,然后 get out of the way


二、Harness 范式的工程设计哲学

理解了范式差异之后,让我们深入看看 Harness 范式在工程层面是怎么落地的。

2.1 “操作系统”而非”框架”

Claude Code 的定位不是一个”调用 LLM 的应用”,而是一个为 LLM 构建的完整操作系统。就像 Linux 为用户程序提供文件系统、进程管理、网络栈一样,Claude Code 为 LLM 提供:

  • 文件系统访问:读/写/精确编辑任意文件
  • 进程执行:运行 Shell 命令,支持超时和中断
  • 代码搜索:文件名匹配 + 内容正则搜索
  • 网络访问:搜索互联网 + 抓取网页
  • 子进程(子代理):递归启动子代理处理复杂任务
  • 外部协议:通过 MCP 协议接入任意外部工具
  • 语言服务器:实时获取代码诊断、定义跳转

这些能力不是简单的 API wrapper,每一个都经过了精心的工程设计。比如文件编辑不是简单的”覆写文件”,而是支持精确的行级编辑,并且会自动检测冲突、处理并发。

2.2 Agent Loop 的极简之美

整个系统的心脏是一个流式的 Agent Loop。它的设计体现了一个深刻的工程洞察:复杂的行为应该涌现自简单的规则,而不是被硬编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
用户输入

┌──────────────────────────────────────────────────┐
│ Agent Loop │
│ │
│ 1. 构建请求 (系统提示 + 历史消息 + 上下文) │
│ 2. 调用 API (流式) │
│ 3. 处理流式响应: │
│ ├── 文本块 → 实时渲染给用户 │
│ ├── tool_use 块 → 立即执行工具 │
│ └── thinking 块 → 内部推理(不展示) │
│ 4. 收集工具结果 │
│ 5. 判断是否继续: │
│ ├── 有 tool_use → 工具结果追加,回到步骤 2 │
│ ├── end_turn → 结束 │
│ ├── max_output_tokens → 自动恢复 (最多3次) │
│ └── prompt_too_long → 自动压缩,回到步骤 2 │
│ 6. Token 预算检查 │
│ 7. Turn 计数检查 │
│ │
│ [循环直到模型决定停止或触发终止条件] │
└──────────────────────────────────────────────────┘

输出结果

注意这里没有任何”if 用户要求重构 then 走重构流程”这样的分支逻辑。所有的任务路由、工具选择、执行策略都由模型在运行时动态决定

2.3 流式执行:与时间赛跑

传统的工具执行是”请求-响应”模式——等 API 返回完整响应,解析出工具调用,再逐个执行。Claude Code 的做法更激进:

在 API 流式返回的过程中就开始执行工具

第一个 tool_use block 的 JSON 一完成解析,就立刻开始执行。后续的 block 继续接收,继续排队。这意味着网络传输和工具执行是重叠进行的,整体延迟大幅降低。

这种设计在编程场景下尤其重要——当模型决定同时读取 5 个文件时,你不需要等 5 个 tool_use block 全部接收完才开始读文件。第一个文件名一确定,文件读取就开始了。


三、如何让 Agent 不丢失上下文

上下文管理是 Coding Agent 最核心也最容易被忽视的问题。一个典型的编程会话可能持续数小时,涉及几十个文件、上百次工具调用。如果每隔 20 分钟就”忘了”之前在干什么,体验就会断崖式下跌。

3.1 问题的本质

LLM 的上下文窗口是有限的。即使是 200K token 的窗口,在一个复杂的编程会话中也会很快填满——每次文件读取、每次命令输出、每条对话消息都在消耗 token。

传统方案很粗暴:要么截断历史消息(丢失上下文),要么限制对话长度(限制能力)。Claude Code 的方案精妙得多:多层压缩 + 持久记忆

3.2 六层上下文压缩策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌──────────────────────────────────────────────┐
│ 上下文管理的六道防线 │
│ │
│ 第一层: Auto Compact (自动压缩) │
│ - 监控 token 使用量 │
│ - 达到阈值时自动触发 │
│ - 将历史对话压缩为简洁摘要 │
│ - 保留关键决策和当前任务上下文 │
│ │
│ 第二层: Micro Compact (微压缩) │
│ - 针对单次工具调用结果的即时压缩 │
│ - 比如一个 grep 返回了 500 行结果 │
│ - 自动提取关键信息,压缩为摘要 │
│ - 缓存压缩结果,避免重复计算 │
│ │
│ 第三层: Reactive Compact (响应式压缩) │
│ - API 返回 prompt_too_long 错误时触发 │
│ - 紧急压缩以恢复对话 │
│ - 这是"最后一道防线" │
│ │
│ 第四层: Snip Compact (裁剪压缩) │
│ - 对历史消息进行智能裁剪 │
│ - 保留开头和结尾,压缩中间部分 │
│ │
│ 第五层: Session Memory (会话记忆) │
│ - 跨压缩边界保持关键信息 │
│ - 即使历史消息被压缩,关键决策仍然保留 │
│ - 持久化到磁盘 │
│ │
│ 第六层: Context Collapse (上下文折叠) │
│ - 实验性的更激进压缩策略 │
│ - 将多轮工具调用折叠为单条摘要 │
└──────────────────────────────────────────────┘

这套系统的精妙之处在于层层递进、各有侧重

  • Auto Compact 是常规操作,温和地压缩历史
  • Micro Compact 解决单次工具返回结果过大的问题
  • Reactive Compact 是紧急兜底
  • Session Memory 确保即使压缩了,关键信息也不丢失

3.3 Session Memory:跨越压缩边界的记忆

这是我认为最精妙的设计之一。

想象一个场景:你和 Agent 讨论了一个小时,做了很多决策——“我们决定用 Strategy 模式重构这个模块”、”这个 API 的返回格式必须保持向后兼容”、”测试覆盖率要达到 80%”。然后上下文满了,触发了自动压缩。

如果只做简单的摘要压缩,这些关键决策很可能被淹没在”读取了 file_a.ts”这样的工具调用记录中。

Session Memory 的做法是:在压缩之前,提取并持久化关键的上下文信息——用户的偏好、重要决策、当前任务状态。即使历史消息被大幅压缩,这些”记忆”仍然会被注入到后续的对话中。

这就像人类的记忆系统:你可能忘了上周二下午 3 点读了哪些代码,但你记得”我们决定用 Redis 做缓存”。

3.4 System Prompt 缓存:被忽视的成本杀手

上下文管理不只是关于”不丢信息”,还关于成本

Claude Code 的系统提示词有近千行,每次 API 调用都要发送。如果不做优化,光系统提示就要消耗大量 token。

它的解决方案是将系统提示分为两部分:

  • 静态部分:工具定义、行为规范、安全规则——这些跨会话不变
  • 动态部分:当前 Git 状态、项目配置、用户自定义规则——这些每次可能不同

通过在两部分之间插入一个特殊的分隔标记,API 层可以对静态部分做全局缓存,Prompt Cache 命中率最大化。据分析,仅仅将代理列表从工具描述移到附件中,就减少了 10.2% 的 cache_creation tokens。

这种”抠 token”的精神贯穿了整个系统。


四、如何让模型减少幻觉

幻觉(Hallucination)是 LLM 的天敌,在编程场景下尤其致命——模型”幻觉”出一个不存在的 API、一个错误的文件路径,就可能浪费用户大量时间。

Claude Code 的防幻觉策略可以归纳为一个核心原则:用环境事实来 ground 模型的每一步决策

4.1 Tool-Grounded Reasoning:用工具结果锚定推理

这是最根本的防幻觉机制。模型不是在”想象”代码库长什么样,而是通过工具调用实际查看代码库。

当模型需要修改一个文件时,它不会凭记忆”猜”文件内容,而是:

  1. 先用 GrepTool 搜索相关代码
  2. FileReadTool 读取完整文件
  3. 基于实际读取的内容生成编辑
  4. FileEditTool 执行精确编辑(而不是覆写整个文件)

每一步都有真实的文件系统反馈。如果文件不存在,工具会返回明确的错误信息,而不是让模型在幻觉中继续。

4.2 环境上下文注入:让模型知道自己在哪

Claude Code 会在每次对话中自动注入丰富的环境上下文:

上下文类型 注入内容
Git 状态 当前分支、最近提交、未提交的修改
项目配置 CLAUDE.md 项目级规则、技术栈信息
LSP 诊断 实时的编译错误、类型警告
文件结构 项目目录树的概览

这些信息让模型在开始推理之前就对”当前环境的真实状态”有准确的认知,而不是基于训练数据中的通用知识来猜测。

4.3 精确编辑 vs 全文覆写

这是一个常被忽视但极其重要的设计决策。

很多 Coding Agent 的文件编辑方式是”生成新的完整文件内容然后覆写”。这有两个严重问题:

  1. 幻觉风险高:模型需要”记住”文件的所有内容,很容易遗漏或修改不该改的部分
  2. Token 浪费:一个 1000 行的文件,改了 3 行,却要重新生成 1000 行

Claude Code 的 FileEditTool 采用的是精确编辑模式——指定要替换的旧字符串和新字符串。模型只需要关注需要修改的部分,大幅降低了幻觉的概率。

4.4 LSP 集成:编译器级别的事实核查

当模型完成代码编辑后,LSP(Language Server Protocol)会立刻提供诊断信息——类型错误、语法错误、未解析的引用等。

这相当于给模型配了一个”编译器级别的事实核查员”。如果模型幻觉出了一个不存在的函数调用,LSP 会立刻报告 Cannot find name 'xxx',模型可以据此自我修正。


五、工具系统的精妙设计

工具系统是 Harness 的核心——它决定了 LLM 能做什么、做得多好。Claude Code 的工具系统有几个令我印象深刻的设计。

5.1 智能并发编排

当模型在一次回复中调用了多个工具时,系统不是简单地顺序执行,而是根据工具的特性自动决定并行还是串行

1
2
3
4
5
6
7
8
9
10
11
模型一次性发出工具调用:
[ReadFile("a.ts"), GrepTool("TODO"), ReadFile("b.ts"), FileEdit("c.ts"), ReadFile("d.ts")]

系统自动分区执行:
┌─ ReadFile("a.ts") ─┐
├─ GrepTool("TODO") ├── 并行执行 (都是只读操作)
├─ ReadFile("b.ts") ┘

├─ FileEdit("c.ts") ←── 串行执行 (写操作,必须独占)

└─ ReadFile("d.ts") ←── 继续执行 (只读操作)

每个工具自己声明”我是否可以安全并发”,系统据此自动编排。读操作之间自由并行(最高 10 并发),遇到写操作立刻切换为串行,写操作完成后再恢复并行。

这种设计在大型项目中效果显著——当模型需要同时读取 5 个文件来理解一个功能的实现时,5 次 IO 操作是并行的,而不是串行等待。

5.2 元工具:ToolSearchTool

当 Agent 接入了大量 MCP 服务器后,可用工具数量可能达到上百个。把所有工具的 Schema 都塞进每次 API 调用会浪费大量 token。

Claude Code 的解决方案是引入一个**”元工具”**——ToolSearchTool:

1
2
3
4
5
工具数量 > 阈值时:
1. 只加载核心工具 + ToolSearchTool
2. 模型需要特定能力时,先调用 ToolSearchTool 描述需求
3. 系统根据语义匹配返回相关工具
4. 模型再调用匹配到的具体工具

这是”延迟加载”思想在工具系统中的体现——不预加载所有能力,而是按需发现和加载。

5.3 子代理系统:分而治之

AgentTool 是整个工具系统中最复杂也最强大的工具。它允许主代理启动子代理来处理子任务,支持多种模式:

  • 同步子代理:阻塞主对话,等待子任务完成
  • 后台异步子代理:不阻塞主对话,子任务在后台执行
  • Fork 子代理:继承父代理的完整对话上下文,共享 Prompt Cache
  • Worktree 隔离:在独立的 Git worktree 中执行,避免冲突

其中 Fork 子代理的设计尤其精妙——子代理不从零开始,而是”站在父代理的肩膀上”。它继承了父代理积累的所有上下文(项目结构、已做的决策、当前任务状态),同时共享 Prompt Cache,几乎不产生额外的缓存成本。

5.4 Coordinator 模式:生产级多代理协作

在更复杂的场景下,Claude Code 可以进入 Coordinator 模式——一个主代理编排多个 Worker 的完整工作流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Coordinator (主代理)

├── Research 阶段 (并行)
│ ├── Worker A: 分析代码结构
│ ├── Worker B: 分析测试覆盖
│ └── Worker C: 检查依赖关系

├── Synthesis 阶段 (Coordinator 自己做)
│ └── 综合所有 Worker 的研究结果

├── Implementation 阶段 (串行)
│ └── Worker D: 基于综合分析实现修复

└── Verification 阶段 (独立)
└── Worker E: 用全新视角验证修复

注意一个关键设计:Synthesis 阶段由 Coordinator 自己完成,而不是委托给 Worker。这确保了主代理始终掌握全局视图,不会在信息传递中丢失关键细节。

Worker 之间通过消息路由系统通信,支持直接消息、广播、关闭请求等多种消息类型,并有完整的计划审批流程。


六、Edge Case 的工程化处理

一个 Coding Agent 从”能用”到”好用”,差距往往在 Edge Case 的处理上。Claude Code 在这方面的工程投入令人尊敬。

6.1 max_output_tokens 截断恢复

LLM API 有输出 token 的限制。当模型的回复太长时(比如在生成一个很长的文件),输出会被截断。

大多数 Agent 遇到这种情况要么报错,要么丢失截断的部分。Claude Code 的做法是自动恢复循环

1
2
3
4
5
6
7
API 返回 stop_reason = "max_output_tokens"

自动将已有的输出追加到消息历史

重新调用 API,让模型继续从截断处生成

最多自动恢复 3 次

对用户来说,这个过程完全透明——你看到的只是一段很长的输出流畅地生成出来。

6.2 prompt_too_long 紧急恢复

当消息历史过长导致 API 返回 prompt_too_long 错误时,不是直接报错,而是触发 Reactive Compact——紧急压缩上下文,然后自动重试。

这个设计的价值在于:用户永远不会遇到”对话太长了,请开一个新会话”这种体验断裂。系统在后台悄悄处理了所有的复杂性。

6.3 Shell 命令的多层安全验证

BashTool 是安全风险最高的工具——它可以执行任意 Shell 命令。Claude Code 为它构建了极其严格的多层验证:

  1. 路径验证:确保命令不操作项目目录之外的路径
  2. sed 命令解析:专门解析 sed 命令判断是否修改文件(因为 sed -i 是一种隐蔽的写操作)
  3. 破坏性命令检测:识别 rm -rfgit push --forcechmod 777 等危险模式
  4. 只读验证:在只读模式下拦截所有写操作
  5. 沙箱限制:在沙箱环境中限制网络访问和文件系统权限

仅这个安全验证模块就超过了 100KB 的代码量。

6.4 权限的分层设计

安全不只是”拦截危险命令”,还需要一个完整的权限体系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
第一层: Permission Mode (权限模式)
├── default: 每次询问用户
├── auto: 自动分类器判断风险级别
└── bypass: 跳过所有权限检查 (仅限信任环境)

第二层: Tool Permission (工具级权限)
├── allow: 始终允许
├── deny: 始终拒绝
└── ask: 每次询问

第三层: Hooks (钩子系统)
├── pre-tool: 工具执行前的自定义校验
├── post-tool: 工具执行后的检查
└── user-prompt-submit: 用户提交前的过滤

第四层: Sandbox (沙箱)
├── 网络白名单
├── 文件系统读写权限
└── Unix socket 限制

这种分层设计让安全策略可以根据场景灵活调整——本地开发可以宽松一些,CI/CD 环境需要严格锁定,企业环境需要审计日志。

6.5 优雅的中断与恢复

当用户按下 Ctrl+C 中断一个正在执行的操作时,系统不是粗暴地终止一切,而是通过 AbortController 体系实现优雅的中断:

  • 正在进行的 API 调用被取消
  • 正在执行的工具收到中断信号
  • 已分配的资源被正确释放
  • 对话状态保持一致

用户可以在中断后继续对话,之前积累的上下文不会丢失。

6.6 Token 预算控制

在 SDK 模式下(作为其他程序的子组件运行时),支持 API 级别的 Token Budget 控制。超过预算时自动停止,而不是无限消耗。

这对于在 CI/CD 中使用 Agent、或者按成本计费的商业场景来说至关重要。


七、MCP 协议:开放的扩展体系

值得单独提一下的是 MCP(Model Context Protocol) 的角色。它是 Claude Code 扩展能力的核心通道。

Claude Code 同时扮演两个角色:

  • 作为 MCP Client:连接外部的 MCP Server,获取新的工具能力
  • 作为 MCP Server:将自己的所有工具暴露出去,供其他程序调用

这意味着:

  • 想让 Agent 操作数据库?接入一个数据库 MCP Server
  • 想让 Agent 管理 Kubernetes?接入一个 K8s MCP Server
  • 想让 Agent 调用内部 API?写一个自定义 MCP Server

无需修改 Claude Code 的任何代码,通过协议扩展能力。这是真正的”开放架构”。


八、总结:好的 Agent 框架就是没有框架

写到这里,我想回到文章开头的问题:为什么同样是”让 LLM 写代码”,不同架构的体验差距如此之大?

答案是:差距不在 LLM 本身,而在 Harness 的深度

一个优秀的 Coding Agent 需要同时做好六件事:

  1. 🔧 丰富的工具:让 LLM 有足够的”手脚”
  2. 🧠 智能的上下文管理:让 LLM 不会”失忆”
  3. 🎯 充分的环境反馈:让 LLM 不会”幻觉”
  4. ⚡ 高效的执行:让交互不会”卡顿”
  5. 🔒 严密的安全:让操作不会”失控”
  6. 🌐 开放的扩展:让能力不会”封顶”

LangChain 时代,我们试图用复杂的编排逻辑来弥补模型能力的不足。如今,模型已经足够强大,最好的策略是:

构建一个极其丰富的 Harness——工具、上下文、安全、协作——然后让 LLM 自己决定怎么做。

这就是 Harness 范式的核心,也是 Claude Code 的成功之处。它证明了一个反直觉的结论:最好的 Agent 框架,就是没有框架

不需要 DAG,不需要 Chain,不需要 Pipe。只需要一个简洁的 Agent Loop + 精心设计的环境,剩下的交给模型自己。


参考资料


这篇文章是我基于 Claude Code 源码研究的思考总结。在 AI 编程助手快速演进的今天,理解架构设计背后的取舍,比知道某个具体功能怎么用更有长期价值。


文章作者: RickDamon
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 RickDamon !
 本篇
从 LangChain 到 Harness:Coding Agent 的架构演进之路 从 LangChain 到 Harness:Coding Agent 的架构演进之路
导语2024 年底到 2025 年初,Coding Agent 赛道迎来了一次范式跳跃。 从最早的 LangChain 式”显式编排”,到 AutoGPT 式”自主循环”,再到如今以 Claude Code 为代表的 Harness 范式—
下一篇 
基于Google最新Agents论文的学习文档 基于Google最新Agents论文的学习文档
导语原链接:Google AI Agents Technical Guide 这份白皮书是 Google 对AI Agent 从概念到生产部署的完整技术路线图,涵盖了: ✅ AI Agent 的定义和分类 ✅ Agent 开发框架(ADK
  目录