Anthropic 在 2025 年底发了一篇 Effective Harnesses for Long-Running Agents,提出了 harness 的概念——用架构手段把不稳定的模型约束成可持续运行的工程系统。后续又在 Harness Design for Long-Running Application Development 里进一步展开了多代理分工和评估分离的实践。
《Harness Engineering:Claude Code 设计指南》这本小册子把这套思路拉得更深,它通过分析 Claude Code 的源码,系统地拆解了 prompt 控制面、执行循环、工具权限、上下文治理、错误恢复、多代理验证和团队落地这些层面的工程设计。书中有大量 Claude Code 源码级的细节分析,本文把它精简掉,只保留可迁移的最佳实践要点。
一、为什么需要 Harness Engineering
- 模型不值得天然信任。 一个会说话的概率分布,一旦能接触终端和文件,风险就从修辞层面升级到执行层面。只会输出文本的模型出错时增加的只是沟通成本,但能调工具的模型出错时留下的是实际后果——文件被删、进程被杀、Git 历史被改写。代理系统要进入真实工程环境,首先要承认自己的核心部件是不稳定的。忽视这一点,问题最后通常会在日志和事故记录里出现。
- 约束执行是核心能力。 模型会犯错,工具会扩大错误后果,上下文会膨胀,状态会污染下一轮,用户会打断你,失败会反复出现。系统不能靠"聪明"维持秩序,只能靠结构维持秩序。结构不像聪明那样显眼,但通常更可靠。一个真正可用的代理系统,不能依赖一段"万能提示词"解决所有问题,它必须把控制拆成层,把层次拆成职责。
- Harness 是一整套控制平面。 它包括 prompt 约束、执行循环、工具调度、权限审批、错误恢复等多个层次,共同指向一个目标:让模型在不可靠的前提下仍然输出可承受的行为。高风险能力必须配高密度约束——能力越强,控制越细,因为外部世界不会因为模型语气坚定就自动原谅一次错误执行。把这些放在一起看,Harness Engineering 并不神秘,它只是坚持几条常被忽视的工程常识。
二、Prompt 是控制平面,不是人格设定
- 人设和控制不在同一层。 人设描述解决的是"它像什么",控制平面解决的是"它能做什么、什么时候做、做错了怎么办、谁来兜底"。一个系统可以有讨人喜欢的人设,同时在执行层面缺少规矩——那种系统出事时往往会显得很真诚,因为它很会道歉,但道歉并不能替代运行时设计。对于一个要读文件、调工具、动 shell、处理权限、跨轮执行的代理系统来说,prompt 更接近一套运行时协议,而不是一篇人物小传。
- Prompt 要分层拼装,不是一段万能文本。 成熟系统不会迷信唯一版本的 prompt,而会把它当成一个有层级的配置系统:身份说明、系统级规则、工程性约束、领域行为各自独立管理。新增提醒和禁令互相冲突时,系统行为就会变得难以预测,拆成层、拆成职责才是正经做法。一个会自动"顺手优化一切"的模型,从产品角度看也许很热情,从工程角度看则相当危险,所以工程性约束(不要越权、不要隐瞒验证失败、不要没必要地制造抽象)需要在 prompt 里被明确规定。
- Prompt 必须有优先级机制。 不同语境(协调者模式、agent 模式、用户自定义)应该有明确的优先级排序,而不是谁后写谁说了算。关键原则是:新增 agent 指令只能在默认约束之上叠加领域行为,不能把整套纪律换掉。可以理解为通用制度外加岗位说明书——岗位说明可以补充职责,但不能冲掉底层制度,否则系统很快就会各自为政。没有结构的可定制,最后往往会退化成另一种随意。
- Prompt 还要连接记忆系统。 成熟的 prompt 不只规定"这一轮怎么执行",还要规定"长期记忆如何形成"——什么该保存、什么不该保存、索引和正文如何分离、plan 和 task 不该被误用成 memory。走到这一步,prompt 就不可能再只是语气问题,而必然进入制度问题。它把 prompt 的职责从"约束当前行为"扩展到了"约束未来知识的沉淀方式",更接近一份写给运行时参与者的知识治理协议。
三、Query Loop:代理系统的心跳
- 代理依赖有状态的执行循环,不是一问一答。 判断一个代理是否成熟,先看它有没有维护跨轮的执行状态:消息历史、恢复计数、压缩追踪、工具上下文、轮次计数等。一旦这样设计,就等于正式承认上一轮留下的问题会进入下一轮,系统必须有能力继续处理。一个系统是否能被称为 agent,往往不取决于它会不会说,而取决于它能不能在几轮之后仍然知道自己在做什么。脚本只关心这一步有没有跑完,代理系统还要关心:这一步失败之后下一步能不能继续承接前面留下的状态。
- 上下文治理先于模型推理。 在调用模型之前,运行时应先完成一串整理工作:截取有效消息、裁剪工具结果、压缩历史、折叠上下文。很多系统恰恰相反:先把大量上下文塞进去,再寄希望于模型自己判断什么重要什么不重要,那种做法看似省事,实际上是在把运行时应承担的责任转嫁给概率分布。不要把从混乱中整理秩序的责任交给模型,而是由运行时完成治理后,再把更干净的输入交给模型。先整理现场,再开始执行,这种做法不追求潇洒,但通常更稳妥。
- 中断和恢复必须有一等语义。 只要系统向外承诺了一段执行,就要在中断时把账补平——不能因为用户打断了就假装前面的动作从未发生。已经发出但尚未完成的工具调用必须生成补偿结果,确保执行轨迹可被解释。恢复不是简单重试,而是按成本和破坏性从低到高逐层尝试。停止条件也要细化区分:streaming 完成、用户中断、prompt 过长、输出截断、hook 阻塞,每种情况走不同路径——把"失败了就重试"和"知道什么时候不该再试"区分开来,是成熟系统的关键特征。
四、工具、权限与中断
- 工具是受管执行接口,不是模型能力的延长线。 工具不是意见,工具是动作,动作会留下结果,结果会接触真实世界。模型提出动作建议,是否放行由运行时、规则和用户决定。权限判定结果至少分三种:允许、拒绝、需要询问。"需要询问"这个第三态很关键——它承认系统自己也不该替用户做所有决定。理解意图不等于拥有授权,更不等于拥有持续授权,系统必须把"会做"和"可以做"分开。
- 工具调度必须保持因果秩序。 工具系统一旦允许并发,就必须回答一个老问题:上下文变化由谁决定、按什么顺序生效。正确做法是即便执行是并发的,语义上的上下文演化仍然保持确定顺序——先缓存修改,再按原始顺序回放。并发可以提高吞吐,但不能破坏因果秩序。没有调度纪律的工具系统,只会把模型的不稳定性放大到外部世界,并发如果不受约束就会扩大事故半径。
- 高风险工具必须区别对待。 像 Bash 这种几乎不受领域边界约束的接口,必须被当成特例——它可以直接接触文件、进程、网络和 Git 仓库,还会带上重定向、管道等复杂 shell 语义。正确的做法是针对它建立专门的权限审查、命令前缀解析、子命令数量上限和详尽的操作规约。高风险能力不应该享受通用能力的待遇,能力越通用越要特殊看管。把 Bash 当成普通工具,往往只是设计上的偷懒。
- 工具系统保护的不只是用户,还有系统自身。 不完整的执行结果、失序的上下文修改、无边界的并发副作用、说不清楚的中断语义——这些问题最先崩掉的往往是系统一致性。约束工具的目的,是让"执行过什么、没执行完什么、为什么停了"始终有一条可追溯的因果链。很多约束表面上是在防止误操作,更深一层是在防止系统自己变成一堆无法解释的状态残片。不能解释的执行轨迹,迟早会变成运维问题、审计问题,或者变成团队里谁也说不清楚的长期隐患。
五、上下文治理:Memory 与 Compact
- 上下文是预算,不是仓库。 信息越多系统越聪明是一种常见迷信。上下文首先是一笔昂贵、易膨胀、还会自我污染的资源,代理系统不是图书馆,模型也不是藏书管理员。长期规则、持久记忆、会话连续性和临时对话应该分层治理,不该混成一锅粥。稳定的团队规范和仓库约束寿命远长于某一轮用户消息,如果全都塞进聊天记录里,要么每轮重复注入浪费上下文,要么靠模型自己回忆迟早失手。
- 记忆入口必须短小。 索引文件天然会被频繁加载,频繁加载的东西一旦变胖,整套上下文就会被它慢慢拖垮。长期记忆应该分成"入口"和"正文":入口负责低成本寻址,正文负责高密度承载。入口文件应该设硬上限——超过了就截断并警告,把细节移到独立文件。入口一旦既当目录又当正文,最后就既不是目录也不是正文,只是一个谁都不愿再读第二遍的烂尾摘要。
- 会话连续性要靠结构化摘要,不靠聊天记录。 会话记忆应该萃取成一种可继续工作的操作说明书:当前状态、踩过的坑、改过的文件、后面该接什么。它不求完整复刻对话,而求压缩出继续干活所必需的骨架。会话摘要的预算同样要受控,优先保留"当前状态"和"错误修正"这些对下一步执行最有用的部分。真正成熟的系统会把"为继续工作保留最有用的部分"当成美德,因为上下文预算是工作内存,工作内存的第一职责是可操作。
- Compact 的目标是重建工作语义,不是写一段好看的总结。 压缩后的上下文必须恢复计划状态、文件状态、技能约束、工具附件等运行时环境——摘要只是中间产物,真正的目标是铺平"继续干活所需的工作底座"。所以 compact 更像一次受控重启,而不是一次聊天总结,旧上下文会被转译成新的工作底座。只做前半截的系统,compact 之后虽然"还记得大概",却已经失去了工具和计划状态,接下来还得再花几轮找回自己。上下文系统应该优先保留能维持行动语义的东西,而不是优先保留看起来信息量最大的东西。
六、错误与恢复
- 错误恢复要分层,不要所有问题都打一把重锤。 判断一个代理系统成熟不成熟,不能只看它回答顺畅的时候有多像个人,而要看它出故障的时候像不像系统。例如 prompt 过长时,先尝试排空已知积压,再做更重的全文压缩,不要一上来就重建世界。好的恢复系统会先试图保住最细粒度的上下文,再在必要时接受更粗糙的摘要替代。有些错误要先交给恢复系统试着处理,再决定是否展示给用户——用户真正关心的通常是系统还能不能继续干活。
- 恢复逻辑必须防止自我回环。 如果 compact 之后还是不行,继续 compact 大概率只是把同一种失败换个姿势再演一次。系统里最危险的错误,是失败分支和恢复分支彼此咬住开始无限自我复制。任何自动恢复机制都必须可计数、可限次、可熔断——不会收手的恢复系统,和不会刹车的汽车差不多,理论上都叫系统,实际上都不该上路。连"修复动作"本身也需要修复策略,因为现实中压缩请求自己也可能因为上下文太长而失败,这时候系统优先级是先恢复呼吸再讨论信息保真度。
- 截断后的最佳恢复是续写,不是总结。 输出被截断时,应该让系统直接从断点继续,而不是先道歉、先 recap、先写一段漂亮的空话。先尝试提升 token 上限直接重跑,如果还不够再追加一条指令让模型从截断处接着写,明确要求不要道歉、不要回顾。每一次截断后的 recap 都会进一步消耗预算并增加语义漂移,最终系统做的就不再是任务本身,而是一轮轮地回顾自己做任务。工程系统真正的礼貌,在于别把用户困在失败态里。
- 中断也是需要语义收尾的失败态。 用户打断不只是"不想看了",而是一次需要正确收尾的状态转移。已经发出但尚未完成的工具调用必须生成补偿结果,确保前面承诺过的动作不会变成悬空债务。错误恢复真正修补的,不只是错误本身,还有系统对自己行为的解释能力——系统能不能说清楚"我刚才试图做什么、为什么没做成、现在是继续还是停止"。解释能力一断,系统就会从工程对象退化成玄学对象。
七、多代理与验证
- Fork 首先是运行时经济学问题。 子代理必须和父代理共享缓存关键参数(system prompt、上下文、工具配置等),否则每次都重新烧一遍 token,看上去像在并行提效,实际只是把浪费并行化。状态隔离是默认伦理——所有可变状态先隔离,可共享必须显式声明,防止子代理的局部混乱污染主线程。子代理最宝贵的地方,恰恰在于它可以避免把自己的局部混乱污染主线程:研究中的误判、临时读到的文件状态、一次性的推理枝杈,如果全都直接写回主上下文,你得到的只会是更快的脏化。
- 研究可以委派,综合理解不能委派。 多代理系统里真正稀缺的是 synthesis——把各个 worker 带回来的局部知识重新压成清晰、可执行、可验证的下一步。协调者必须读懂研究结果再写具体指令,后续 prompt 里必须出现具体文件、具体位置、具体变更,而不是抽象地"根据前面的结论"。缺少这一层,多代理很快就会退化成一种带着礼貌措辞的任务转发机——每个 agent 都在忙,系统整体却并没有更懂。这是非常正统的工程分工:研究可以分布式,但理解必须重新收束。
- 验证必须独立成阶段。 "我改了代码"和"代码因此正确"之间隔着一条很宽的河,模型尤其擅长在这条河上搭纸桥。实现者天然倾向于相信自己的改动差不多行了,模型更是如此——它会给你改动、解释、甚至给你一段像样的测试输出,但这些都不等于功能真的站住了。验证要成为独立角色:实现的人专注于改,验证的人专门怀疑这些改动配不配活着。验证的目标是证明代码有效,而不只是确认代码存在——否则"完成"很快就会退化成"已经写完并且我觉得没问题"。
- 子代理需要完整的生命周期管理。 启动时可以观测,停止前可以介入,转录路径可追踪,父任务中止时子任务必须跟着中止。输出文件是否该保留、清理回调有没有泄漏、agent 结束后的状态残留怎么处理——这些都要显式处理。多代理 demo 只做到"能再起一个 agent"远远不够,必须把 agent 当作会泄漏资源、会残留状态、会在父进程结束后变成孤儿的运行实体来看待。多代理真正有价值的地方,在于把不同种类的不确定性关进不同容器里,再用协调者组织回来。
八、团队落地
- 先画最低可控边界。 团队起步不必上 hooks 和复杂 skill 目录,先把四件事讲清楚:哪些任务允许 agent 参与、哪些改动必须经过人工 review、改完至少跑什么验证、哪些资源一律不能碰。这四件事比任何宏大口号都重要——允许范围不定义,大家会拿 agent 做不该自动化的事;review 责任不定义,出问题时没人知道最后一层把关是谁;禁区不定义,效率提升只是扩大事故半径。很多团队最后失败,往往不是因为 agent 不够强,而是因为起步时跳过了这一步。
- 先统一验证定义,再扩 skill 数量。 落地 AI 编码代理最常见的失败不在 prompt、不在 model,而在团队对"完成"没有统一定义。有人觉得能跑就行,有人觉得测试过一半就行,有人觉得模型解释得挺像样也行,这样一来再聪明的系统也只能学会迎合最低标准。Skill 可以复制流程,但只有验证定义才能复制质量——先定义哪些任务必须有独立验证、验证至少包含哪些动作、验证失败时怎么标记,这三件事统一了,即使 skill 很少,质量底线也能稳住。
- CLAUDE.md 更像地基,不是公告栏。 团队级指令文件适合承载稳定规则:代码库硬约束、统一验证口径、协作纪律、输出风格。不适合堆频繁波动的临时流程、只有少量任务才用到的操作细节、本来更适合沉淀成脚本或 skill 的步骤。一旦被写成百科全书就会丧失稳定性和可信度,团队成员不再确定它写的到底是现行规则还是半年前遗留的讨论。系统也会学会一种很糟糕的模式:把过期规范当现行法律。
- Approval 按风险分层,hook 放在最后引入。 权限审批应该按不可逆性和环境敏感度分层(读操作 < 写操作 < 推代码/访问敏感环境),而不是按工具名字一刀切,因为团队真正要控制的是后果而不是按钮名称。Hook 是高级自动化接口,适合在基础治理已经稳定之后再引入——否则它很容易引入新的复杂度:脚本没人维护、触发时机没人说清、调试成本比人工操作更高。更成熟的判断是先用 review、CI 和最少说明文件把底线稳住,再考虑更复杂的编排。
九、十条原则
原书最后将全部内容收束为十条原则:
- 把模型当不稳定部件,不要当同事。 模型也许能像同事一样说话,但它不会自动获得稳定性、责任感和持续判断力。越早承认这一点,系统就越早开始补上权限、恢复、验证和回滚。
- Prompt 是控制平面的一部分。 它和 runtime、工具 schema、memory、hook 一起组成控制平面。把 prompt 当人格设定,最后你会得到一个很会表演但不受约束的系统。
- Query loop 才是代理系统的心跳。 输入治理、流式消费、工具调度、恢复分支、停止条件,都是心跳的一部分。没有执行循环的系统也许能做 demo,但还谈不上运行时。
- 工具是受管执行接口。 一旦模型开始碰 shell 和文件系统,问题就从"它会不会说"变成"它会不会留下后果"。越危险的工具,越不能按普通能力对待。
- 上下文是工作内存。 能塞进上下文不等于应该塞进去。Compact 的目标是保住继续工作的语义底座,标准不是"够多"而是"可治理"。
- 错误路径就是主路径。 Prompt 过长、输出截断、中断、hook 回环、compact 自身失败,都是长会话代理的日常天气。恢复和熔断必须在设计时就存在。
- 恢复的目标是继续工作。 截断之后最好的动作通常是续写;压缩失败时最重要的是先让系统恢复呼吸。
- 多代理的意义是把不确定性分区。 研究、实现、验证、综合放进不同容器,由协调者收束理解。并行真正带来的价值不是更快,而是让职责边界更清楚。
- 验证必须独立,不能让系统自己给自己打分。 凡是重要任务,验证都应该成为独立阶段,最好还有独立角色。
- 团队制度比个人技巧重要。 一个高手可以靠经验把代理驯服,一个团队不行。只有把个人经验制度化,代理系统才可能成为组织能力,而不是个人把戏。