Harness 工程:不只是给 Agent 包一层
Harness 这个词是今年突然火起来的。最早是 Anthropic 工程师 Justin Young 在 2025 年底写了篇文章叫《Effective Harnesses for Long-Running Agents》,然后 OpenAI 在今年初把它命名为一门独立学科,Martin Fowler 网站给了个定义,LangChain 的人提炼出公式:Agent = Model + Harness。于是这个词开始铺满各种 AI 工程的文章。
但看了一圈,要么是纯概念科普讲不清楚边界,要么是厂商 demo 看着很酷、真去落地一脸懵。我自己陆续搭过几个 Agent 系统,从单轮的工具调用到跑几小时的长任务,把这些经历和这些文章对照了一下,写下来聊聊我的理解——包括几个我觉得被普遍低估的点。
「不是模型就是 Harness」
LangChain 的 Vivek Trivedy 那个定义我觉得是最准的:如果你不是模型本身,你就是 Harness。
换成工程语言就是:上下文怎么组装、工具怎么注册和调用、状态怎么持久化、错误怎么处理、循环什么时候停——这些事情全部加在一起,就是 Harness。有人把它理解成「给 LLM 写个胶水层」,差一点,胶水层是一段脚本,Harness 是一套系统,区别在于它要管生命周期。
有个类比我觉得挺传神的:原生 LLM 就是一个没有内存、没有硬盘、也没有 I/O 设备的 CPU。上下文窗口是内存(快但有限),外部数据库是硬盘(大但慢),工具调用是设备驱动。Harness 就是操作系统——不是 CPU 的一部分,但 CPU 离了它什么也干不了。
这个类比解释了为什么「Prompt Engineering」和「Context Engineering」都不足以描述这件事。Prompt 是你怎么跟 CPU 下指令,Context 是你往内存里装什么,而操作系统管的是任务调度、进程隔离、错误恢复、权限控制,是另一个维度的问题。
编排循环是核心,但不是全部
生产级 Harness 里最基础的部件是编排循环,就是那个 Reason → Act → Observe 的 while 循环。实现起来其实没多复杂,粗略看就是:
while not done:
response = llm(build_context(state))
tool_calls = parse_tool_calls(response)
observations = execute_tools(tool_calls)
state = update_state(state, response, observations)
done = check_termination(state)
但「复杂的地方不在循环本身,而在循环要处理的各种状态和边界」——这句话我在几篇文章里都看到了,说得准。
真正让我头疼的是这几个边界:
终止条件。Agent 什么时候该停?如果让它自己判断,它要么过早宣布完成(任务做到一半就说好了),要么陷入无限循环不知道自己在转圈。我现在的做法是显式设置两个上限:最大工具调用次数(一般 50~100 次)和最大耗时,触发任一个都强制终止并汇报进度。
上下文填充率。Anthropic 的数据说上下文窗口超过 40% 填充率后输出质量会快速衰退,这个我自己测下来大体符合。但很多人不知道这个数字,默认把工具定义、历史消息、检索结果全塞进去,然后抱怨模型变笨了。我现在给每个 Agent 任务设置一个「上下文预算」,工具定义不超过 15%,历史消息不超过 30%,剩下留给当前任务素材。
工具执行失败。工具报错后应该告诉 Agent 然后让它重试,还是直接终止?这没有统一答案,取决于工具的语义。数据库查询失败大多数情况下重试没意义(参数就是错的),但文件操作失败可能是路径问题、重试前让 Agent 反思一下有价值。我的规则是:幂等操作允许重试一次,非幂等操作(写入、发送消息)失败直接停,必须人工介入。
两个被低估的组件
大家写 Harness 文章经常花大量篇幅讲工具和记忆系统,但我觉得两个更重要的组件被低估了:状态持久化和护栏(Guardrails)。
状态持久化不只是「把对话历史存到数据库」。更重要的问题是:Agent 在执行过程中产生的中间状态——部分完成的任务、已发现但未处理的问题、用过又释放的资源——这些东西如果丢了,整个任务就要重头来。我现在让 Agent 维护一个外部的 task_state.json,每个关键动作后强制写入,类似数据库事务的 WAL。这个文件也成了 Debug 最有用的工件,出问题了直接看这个文件,能还原 Agent 在哪一步、基于什么信息做了什么决策。
护栏则是很多团队等出了安全事故才补的东西。我之前有个 Agent 被输入里的一段文本诱导,调用了它本不应该调用的删除工具——prompt injection 这件事真实发生过,不是理论风险。现在我的标准做法是:写入类、删除类工具,调用前强制走一次人工确认(MCP 的 elicitation 机制可以做到);工具的返回值和工具的参数一样,都是不可信输入,要过内容检测。
Harness 会被模型吃掉吗
Anthropic 的产品负责人 Cat Wu 在访谈里说了句很有意思的话:「harness 会被模型当早餐吃掉」。意思是随着模型能力提升,以前需要 Harness 来补偿的能力会被模型原生支持,Harness 的边界会不断后退。
我觉得这个判断对,但不完整。模型会吃掉的是那些「因为模型不够能干才需要在外面额外做的部分」——比如更繁琐的 COT 提示、更复杂的记忆检索策略。但有些 Harness 的功能跟模型能力无关:状态持久化、执行隔离、权限控制、工具调用的幂等保证——这些是系统工程问题,不是模型智能问题,模型再强也不能替你解决服务挂了状态丢了的问题。
所以我对「Harness 早餐」这句话的理解是:Harness 里跟「模型聪不聪明」有关的部分会逐渐缩水,跟「系统可靠不可靠」有关的部分会越来越是核心。工程师在 Harness 上应该花的时间不会减少,只是花在不同地方。
目前我们内部有两个 Agent 系统在生产环境跑着,一个做代码审查,一个做日志分析。Harness 这层加起来的代码量已经比业务逻辑多了。刚开始觉得这不对劲,后来意识到这大概就是对的状态——模型是引擎,Harness 是车,引擎越强你越需要一辆好车来承载它。