源码地址:https://github.com/wquguru/harness-books
如果你最近在关注 AI 编程工具,一定听说过 Claude Code。很多人把它看作一个“能跟你聊天、顺手改代码的 CLI”。但如果只看到这一层,就错过了它设计里最有价值的部分。
真正让 Claude Code 区别于“挂了个工具调用的 ChatGPT”的,是一整套看不见的控制结构。作者在源码里把它称为Harness Engineering——你可以理解为给 AI 代理造一个驾驶舱。
本文从工程视角,聊清楚一个核心判断:
代理系统的关键能力,不是“模型有多聪明”,而是“系统有多能约束执行”。
一、问题本质
1.1 核心矛盾
一个 LLM 如果只能输出文本,最坏的情况是“它说得不太对”。但当它能访问 Shell、Git、文件系统和网络时,问题性质就变了——从“回答不够好”变成了“执行可能造成实际破坏”。
Harness Engineering 要处理的就是这个底层现实:
模型天然不值得信任。
这不是对 AI 的道德评判,而是一个工程前提。忽视它的系统,最后多半会在事故记录和回滚日志里付出代价。
1.2 设计目标
所谓 Harness Engineering,就是把一个不稳定的核心部件约束成可管理系统的那一套制度化控制平面。它要处理的现实只有一条:模型并不天然值得信任。这个判断未必轻松,但通常有用。
二、第一层 Harness:分层控制结构
很多人对“控制 AI”的理解还停留在写一段漂亮的 system prompt。但 Claude Code 在实现上,把 prompt 放进了运行时的控制结构里——它不只是“告诉模型该怎么做”,而是规定了执行边界、失败行为和报告责任。
2.1 身份与运行时分离
从源码的组织方式可以看得很清楚:
系统先定义身份和总任务
再补上关于工具、权限、系统提醒和上下文压缩的系统级说明
最后补上做任务时的工程约束,比如不要越权改动、不要把验证说成已经完成
这说明了一个朴素的工程事实:
一个真正可用的代理系统,不能依赖一段“万能提示词”解决所有问题。它必须把控制拆成层,把层次拆成职责。
否则,新增一条禁令和旧规则冲突时,系统行为会变得不可预测。
2.2 分段拼装与优先级规则
代码里很清晰地展示了这一点:
系统提示词是分段拼装的,静态部分和动态部分严格分离
memory、language、output style、MCP instructions 等内容按段注入
默认 prompt、自定义 prompt、agent prompt 和 append prompt 之间有明确的优先级规则
2.3 不变式:三条硬约束
1. 身份 ≠ 运行时 —— prompt 的分层必须完整 2. 工具受调度 —— 任何工具调用前必须经过调度决策 3. 错误进主路径 —— 可恢复错误必须走规定的恢复或终止路径
这三条一旦被越过,后面所有关于循环、工具、上下文和恢复的机制都会变得不可靠。
三、第二层 Harness:持续循环状态
如果说 prompt 规定了系统“应该成为什么样的东西”,那么 query loop 规定了它“实际上如何运行”。
3.1 状态跨轮次传递
Claude Code 的核心不在某个单独的 API 调用,而在一个持续运行的 query loop。这个循环把以下内容全部放进同一个跨迭代状态里:
messages(消息历史)
toolUseContext(工具使用上下文)
autoCompactTracking(自动压缩追踪)
maxOutputTokensRecoveryCount(输出令牌恢复计数)
turnCount(轮次计数)
transition(状态转换)
一旦这么设计,就等于正式承认:
上一轮留下的问题会进入下一轮,系统必须有能力持续处理。
3.2 Harness 思维的核心问题
真正的问题不在于模型能不能答对这一轮,而在于系统能不能在连续多轮里保持行为一致:
有没有预算概念?
有没有恢复概念?
有没有上下文膨胀后的自救机制?
有没有在工具调用失败后继续推进任务的能力?
缺少这些结构,所谓的“智能体”就只是一个不稳定的执行者。
3.3 调用前收回控制权
这个循环在每轮调用前还会处理:
消息裁剪
工具结果预算
历史片段
微压缩
上下文塌缩处理
自动压缩
这些实现细节共同指向一点:Claude Code 在调用发生前就尽量把控制权收回到运行时一侧。
这也是为什么 Harness Engineering 不能被看作 prompt engineering 的附属品——前者关心状态机,后者关心措辞。措辞当然重要,但状态机决定系统行为最终由谁负责。
四、第三层 Harness:工具调用调度
模型一旦能调用工具,风险就从修辞层面跳到执行层面。这时候最核心的问题是:
谁决定工具怎么跑?
4.1 并发调度纪律
Claude Code 的答案很明确:运行时会根据工具属性决定并发还是串行。
工具调用先经过分组
系统读取工具 schema,判断一个工具是否适合并发执行
能并发的归一批,不能并发的按顺序逐个执行
并发路径中,上下文修改器会先缓存,再按原始顺序回放
4.2 核心原则
这背后有一个重要原则:
工具没有被当成模型能力的自然延伸,而是被当成需要调度纪律的受管执行单元。
缺少调度纪律的工具系统,只会把模型的不稳定性放大到外部世界。尤其是在涉及文件、终端和权限的场景里,保守的调度策略往往比激进的并发更可靠。
4.3 为何保守
并发如果不受约束,就会扩大事故半径。Claude Code 在这一点上采取了偏保守的策略——在会碰到文件、终端和权限的场景里,这种保守通常更可靠。
五、第四层 Harness:高风险工具的高密度约束
在所有工具里,Bash 最值得警惕。它几乎不受领域边界约束,可以直接接触文件、进程、网络和 Git 仓库,还会带上重定向、管道等复杂 shell 语义。
5.1 操作规约
Claude Code 对 Bash 的态度,体现在一段详细的操作规约里:
不要乱改 git config
不要跳过 hooks
不要随手
git add .不要在 pre-commit 失败后用
--amend连带修改上一条提交不要在没有明确要求时提交
不要默认 push
5.2 核心原则
有人会觉得这些规则过于细碎。但高风险接口天然需要高密度约束。Bash 一旦进入真实工作流,很多规则必须明确写出来,不能靠模型的“常识”去判断。
Harness Engineering 的一个重要原则就是:
能力越强,控制越细。
原因很简单:外部世界不会因为模型语气坚定,就自动原谅一次错误执行。
六、第五层 Harness:失败是主路径
很多软件把失败路径看作“例外”,把成功路径看作“正文”。代理系统不能这么做。
6.1 失败稳定存在
代理系统的失败是稳定存在的,不是偶发的:
模型会超 token
会触发
prompt too long会撞上
max_output_tokens会遇到工具拒绝、用户打断、API 重试
还会遇到 hook 阻塞等各种中断
6.2 结构化处理
如果这些情况都只在最后用几个catch打发掉,系统表面上在运行,实际上只是不断把麻烦往后滚。
Claude Code 在 query loop 里把失败当作结构性条件来处理——在调用发生前就尽量把控制权收回到运行时一侧。
6.3 与普通助手的差别
普通助手先回答,错了再道歉;Harness 先约束,再执行,出错按恢复路径处理,不靠临场发挥补救。
一个会道歉的系统不一定成熟。一个知道何时不该开始、何时该重试、何时该中止、何时该准确汇报失败的系统,才更接近成熟。
七、总结
7.1 五个源码位置指向同一结论
| 源码位置 | 结论 |
|---|---|
constants/prompts.ts | prompt 是控制平面的一部分,不是人格装饰 |
utils/systemPrompt.ts | 系统行为必须有清楚的分层优先级 |
query.ts | 代理运行依赖持续的循环状态,不是单次问答 |
services/tools/toolOrchestration.ts | 工具调用必须服从调度纪律 |
tools/BashTool/prompt.ts | 高风险工具必须伴随高密度约束 |
7.2 Harness Engineering 不神秘
它只是坚持了几条常被忽视的工程常识:
模型会犯错
工具会扩大错误后果
上下文会膨胀
状态会污染下一轮
用户会打断你
失败会反复出现
7.3 最终结论
系统不能靠“聪明”维持秩序,只能靠结构维持秩序。
结构不像聪明那样显眼,但通常更可靠。