模型负责产生可能,Harness 负责决定哪些可能可以发生。
最近一段时间,我一直在做 Agent 相关的开发和系统设计。随着实现逐渐从模型调用、Prompt 和工具接入,走向状态、权限、中断、记忆、并发与恢复,我对「Agent 工程到底在解决什么问题」也有了一些阶段性的感受。恰好最近读到碳基智的《Harness 其实就是控制论》,其中不少观点和开发过程中的思考产生了呼应,于是有了这篇文章。
这不是对 Harness Engineering 的完整定义,也不是一套已经被充分验证的方法论,只是我结合当前开发实践和阅读形成的一些个人理解。其中难免存在不成熟或值得讨论的地方,写下来主要是为了整理自己的思路。
读这篇文章时,我最直接的感受不是「又出现了一个新的 Agent 概念」,反而是松了一口气。过去一年,从 Prompt Engineering 到 Context Engineering,再到 Harness Engineering,Agent 领域的新词几乎按季度出现。每个新词都像是在宣布:之前关注的东西已经过时,又需要重新学习一套方法。
但这篇文章提醒我,名词虽然在变,工程面对的核心问题并没有变:如何让一个内部机制无法被完全解释、行为又不完全确定的系统,稳定地朝着人的目标运行。大模型恰好就是这样一个黑箱。没有人能准确解释某个 Token 为什么在此刻出现,也不能依靠一段足够长的 Prompt,穷尽模型可能发生的所有行为;真正能做的,是控制它看到的信息,限制它可以选择的动作,观察执行结果,并在偏离目标时及时纠正。
从这个角度看,System Prompt、上下文、结构化输出、工具权限、Eval、重试和人工审批,确实可以组成一套反馈控制系统。这个视角也让我重新审视了自己此前对通用 Agent 系统的设计,以及 Blades develop 分支提供的运行能力。最初我关注的仍然是模型能不能理解任务、选择工具、完成规划;但当问题进入状态、权限、中断、记忆、并发和恢复以后,真正困难的部分逐渐从「Agent 能做什么」变成了「谁拥有最终控制权」。
我当前更倾向于这样理解:
生产级 Agent 工程,不是试图消灭模型的不确定性,而是在不确定的智能外面,建立一个确定性的控制系统。
这套系统,就是我目前理解的 Harness。
设计 Agent 系统时,人很容易把主要注意力放在模型身上:Agent 应该有什么人设,Prompt 应该怎么写,应该给它哪些工具,是否需要一个 Orchestrator,多个 Agent 应该怎样协作,模型能不能自己规划并完成任务。这些问题当然重要,但它们主要解决的是「Agent 能不能做」。
生产系统还必须回答另一组问题:Agent 为什么在此刻运行,谁允许它运行,它可以看到哪些信息、提出哪些动作,哪些动作能够真正改变外部状态,用户改变意图后旧任务是否应该继续,运行中断后系统从哪里恢复,Agent 产生的内容哪些可以成为系统事实,以及 Prompt、模型和工具升级后,历史行为还能否解释和复现。
这两组问题的区别,实际上就是智能执行面与确定性控制面的区别。智能执行面负责理解、生成、规划和提出候选动作;确定性控制面负责管理状态、校验权限、执行副作用、记录结果、发现偏差和恢复现场。模型可以说「下一步应该调用这个工具」,但这句话本身不是业务事实,真正的工具调用是否发生,仍然要由模型外部的系统决定。

生产级 Agent:智能执行面与确定性控制面
模型负责提出候选动作;状态机、权限、预算和工具网关决定动作能否真正进入外部系统。
换句话说:
Prompt 描述期望行为,控制面定义允许发生的行为。
带着这个视角再看 Blades develop 分支,很多设计会变得非常清晰。Blades 的核心不是一个庞大的业务编排平台,而是一个事件驱动的 Agent 执行框架。
它定义了统一的 Agent 接口。Agent 接收输入事件流,返回输出事件流;一次运行不只是返回最终文本,还会产生模型增量、完整响应、工具开始、工具结束、Turn 结束、错误和完成等事件。这件事很重要,因为控制一个系统的前提,是系统状态必须能够被观察。
Blades 没有把 Agent 简化成一个黑盒式的「输入文本 → 等待 → 输出文本」,而是把运行过程展开成一条事件流。应用层不必理解模型内部发生了什么,也可以知道模型何时开始输出、调用了什么工具、工具是否失败、一次 Turn 为什么停止,以及本次运行消耗了多少 Token。从控制论的角度看,事件流就是 Harness 的传感器层。
在输入侧,Blades 区分了几类不同的控制信号:Prompt 用于开始一个新的 Turn,Steer 用于在步骤边界注入补充信息或修正,Abort 用于在步骤边界终止当前 Turn。这里需要特别说明:当前的 Steer 和 Abort 主要在模型步骤或工具执行波结束后的边界被消费,并不等于可以在任意时刻立即抢占正在执行的模型流或工具调用。更强的即时中断,还需要 context.Context 取消、应用层运行状态和副作用治理共同完成。
这个细节反而很能说明 Harness Engineering 的特点:「支持打断」不是一个简单的布尔值,而是必须明确打断发生在哪个边界、正在进行的副作用如何处理、运行状态最终落在哪里。
Blades 内部的 Agent Loop 基本遵循「模型—工具—模型」的循环:模型产生工具调用,运行时执行工具,将工具结果写回 Session,然后再次构建上下文并调用模型。如果一次响应包含多个工具调用,当前实现会并发执行这一批 Tool Call。并行能力能够降低延迟,但同时也放大了副作用风险。并发读取多个信息源通常没有问题;如果工具涉及写库、发消息、扣费或修改外部状态,应用层仍然需要处理幂等、依赖顺序、冲突和部分成功。
如果把具体的 Go 实现压缩掉,Blades 的一次 Turn 大致可以写成下面这样:
开始 Turn:
把用户输入写入 Session
循环:
Request = 根据当前 Session 重新构建上下文
Response = 调用模型,并持续输出流式事件
如果模型没有请求工具:
把 Response 写入 Session
在步骤边界消费 Steer / Abort
结束,或者带着 Steer 再运行一步
如果模型请求了工具:
发出 ToolStart
执行这一批 Tool Call
发出 ToolEnd
把模型消息和工具结果写入 Session
在步骤边界消费 Steer / Abort
如果收到 Abort:结束当前 Turn
否则:进入下一轮「模型—工具—模型」
结束 Turn:
输出 StopReason、Usage 和 Action
在这段循环里,事件不只是日志,而是应用控制面的观测入口。Abort 和 Steer 的消费位置也说明了一个重要边界:它们主要在一次模型步骤或一批工具执行完成后生效;如果需要立即取消正在运行的模型或工具,还要结合 Context 取消和应用层状态管理。
在工具真正执行之前,Policy 可以进行允许、拒绝、要求审批或修改参数等判断;Hook 则覆盖 Turn、模型调用和工具调用前后的生命周期,可用于观测、审计和扩展处理。不过,当前代码中的 Ask 仍然主要是一种策略语义:运行时会返回「需要审批」的工具错误,但完整的审批等待、状态持久化、批准后恢复执行,仍然需要应用层补齐。
省略并发调度以后,一次 Tool Call 更接近下面这条受控管道:
执行 Tool Call:
call = BeforeTool(call)
decision = Policy.Check(call.tool, call.input)
如果 decision == Deny:
返回「策略拒绝」
如果 decision == Ask:
返回「需要审批」
// 当前只表达策略语义,不会自动挂起等待审批
如果 decision == Modify:
确认工具身份没有被替换
使用修改后的参数继续
如果 decision == Allow:
result = Tool.Handle(call.input)
result = AfterTool(call, result)
发出 ToolEnd(result)
返回标准化的 Tool Result
Policy 负责决定「能不能做、用什么参数做」,Hook 负责在模型和工具生命周期前后提供观测与扩展点。两者共同构成执行内核里的控制闸门,但完整的审批流、审计持久化和恢复机制,仍然属于应用控制面。
Session、Prompt Builder、Context Builder 和 Compactor 共同决定了每一步模型实际能看到什么。Session 提供消息和状态抽象;每次模型调用之前,Context Builder 都会重新读取消息、构建 System Prompt、加入当前工具集合,并在超过 Token 阈值时执行上下文压缩。这说明上下文并不是一段静态 Prompt,而是一个每一步都可以重新计算的控制面。
Blades 还提供 routing、sequential、parallel、loop 等 Flow,也允许把一个 Agent 包装成另一个 Agent 可以调用的 Tool。这些能力解决了 Agent 的组合问题,让不同 Agent 可以被路由、串联、并行或循环执行,但组合不等于治理。这些 Flow 本身并不天然提供持久化工作流、分布式 lease、业务事务、幂等或故障恢复;Blades 自带的 Session 实现也主要是内存形态,它提供了可以扩展的抽象,但不代表生产级持久化和重放已经自动完成。
因此,我目前更愿意把 Blades 定位为「事件驱动的 Agent 执行内核」:

Blades 在完整 Harness 中的位置
Blades 解决 Agent 如何运行;应用控制面继续补齐状态、权限、版本、持久化、审计和恢复。
Blades 解决 Agent 如何运行;应用系统还要决定为什么运行、是否允许运行,以及运行结果如何成为业务状态。
在多 Agent 系统里,引入 Orchestrator 是一个很自然的选择。它可以理解当前目标,判断下一步该由哪个 Agent 执行,也可以把复杂任务拆成多个步骤。在 Blades 中,Agent 可以被包装成 Tool,模型还可以在一次响应中提出多个工具调用,这些能力都很适合表达 Agent Loop 和多 Agent 协作。
但我不认为一个 Agent 只要被命名为 Orchestrator,就天然成为了系统最终的控制器。只要 Orchestrator 仍然由 LLM 驱动,它就仍然具有模型固有的不确定性:可能选错 Agent,构造错误参数,重复调用工具,陷入循环,或者在不合适的时机继续执行。
因此,我当前的设计倾向是让 Orchestrator 负责提出结构化动作,例如调用哪个 Agent、调用哪个工具、等待更多输入、结束当前回合,或者将任务移交给其他执行者。这些动作仍然只是候选决策,不能直接等同于业务事实。它们需要经过模型外部的确定性校验:当前状态是否允许、发起者是否有权限、工具是否在授权集合中、参数是否符合 Schema、是否超过步数和成本预算、是否已经执行过、是否需要人工审批,以及失败后能否安全重试。
所以在我目前的理解中,真正的控制器不是某一个 Orchestrator Agent,而是由状态机、运行时、策略系统、工具网关、事件存储和人工审批共同组成的控制面。Orchestrator 提供智能决策,控制面保留最终裁决权。

Orchestrator 动作的安全执行流程
Orchestrator 的输出只是候选动作;状态、权限、参数、幂等和审批共同构成模型外部的确定性控制链。
《Harness 其实就是控制论》讨论了 Agent 为什么会「越跑越蠢」:模型在早期步骤里产生一个错误,这个错误被写回上下文;下一轮模型把它当成事实继续推理,又产生更大的错误。错误不断进入工作记忆,最终形成正反馈。
在实际工程里,错误传播的范围不只限于上下文。它还可能进入长期记忆、用户画像、任务状态、Agent 之间的消息、Orchestrator 的后续计划、工具产生的外部副作用、自动生成的 Few-shot 样本,甚至后续版本的 Prompt。
因此,我当前有一个比较强烈的判断:
Agent 最危险的能力,不是生成错误,而是把错误升级为状态。

错误输出如何升级为系统状态
错误一旦未经校验进入上下文、记忆或任务状态,就可能在后续推理中持续放大;独立校验负责切断这条正反馈链。
模型生成一段不准确的文字,影响可能只停留在当前响应;但如果这段文字未经校验就进入长期记忆、业务数据库或下一个 Agent 的输入,它就从「一次错误输出」变成了「系统事实」。这也是为什么通用 Agent 系统必须区分不同性质的数据:正式消息是可以被用户看到和回放的事实,Agent Event 是运行过程中的可观测记录,Thinking 不应自动成为下一轮事实,Tool Result 是外部工具返回的原始结果,而模型对 Tool Result 的解释仍然只是模型输出。
长期记忆同样不能由聊天 Agent 随意写入。它至少需要经过提取、来源校验、去重、合并和过期治理;运行摘要可以帮助压缩上下文,但不能覆盖权威业务状态。系统恢复时,也不能只读取模型上一次留下的总结,然后让它「接着想」,而应从持久化消息、运行状态、工具结果和版本配置中重新构建上下文。这本质上是在切断错误形成正反馈的传播路径。
控制论提供了理解 Agent 系统的视角,但要进入生产环境,还需要把这个视角转化为可以实现和验证的工程规则。
上下文越多,不代表控制能力越强。无关信息、过期信息和未经验证的信息,同样会扩大模型的输出空间。上下文工程真正需要解决的是:当前这一次决策,模型应该看到哪些足以完成任务、又不会扩大无关不确定性的信息?
一个通用 Agent 系统至少要区分当前用户输入、最近对话、已确认的业务状态、与当前目标相关的记忆、授权工具、运行预算,以及不同来源信息的可信等级。长期记忆首先是一个写入治理问题,然后才是检索问题;如果写入阶段没有来源、置信度、有效期和权限控制,那么召回做得越好,错误传播得可能越快。
工具是 Agent 从「生成内容」走向「改变世界」的边界。因此,Tool Call 必须经过权限校验、参数校验、幂等控制、超时控制和审计,高风险动作还需要人工审批或更严格的业务规则。Policy 可以决定一类工具调用是否允许,Hook 可以观察和处理执行过程;但审批等待、分布式幂等、跨工具事务和补偿逻辑,仍然属于应用控制面。
Prompt 很难覆盖所有失败模式。模型可能产生幻觉、格式错误、工具越权、重复执行、无限循环、错误记忆和过长输出,也可能在用户意图已经变化以后继续执行旧任务。生产级 Harness 不能只有一条「失败后重新 Prompt」的反馈链,每一种重要失败模式都需要对应的观测信号和控制机制。
失败模式 | 观测信号 | 控制机制 | 结果状态 |
|---|---|---|---|
输出格式错误 | Schema 校验失败 | 修正或有限重试 | retryable |
工具越权 | Policy 拒绝 | 阻止执行并记录 | denied |
重复副作用 | 幂等键冲突 | 返回既有结果 | succeeded |
Agent 循环 | 步数、Token、时间超限 | Budget 中止 | exhausted |
错误记忆 | 来源或置信度不足 | 拒绝写入 | discarded |
并发运行冲突 | lease 冲突 | 等待或放弃本轮 | waiting |
用户改变意图 | 新输入或取消信号 | 中止旧 Run、重新规划 | interrupted |
失败必须能够被分类。系统要知道它是否可以重试、是否已经产生副作用、是否需要人工介入,以及应该恢复到哪个状态。只有这样,Agent 才真正具备「可恢复」,而不仅是「可以重新运行」。
在 Demo 阶段,直接修改 Prompt、模型参数和工具列表,然后重新启动服务,是很自然的做法。但在生产系统里,这些配置共同决定了 Agent 的行为,它们不是普通配置项,而是 Agent 的可执行定义。
因此,我当前倾向于把 Agent 拆成几个不同概念:Identity 表示长期稳定的 Agent 身份;Revision 表示某一版不可变的 Prompt、模型配置、策略和工具绑定;Deployment 表示当前身份正在使用哪个 Revision;Session 则固化实际使用的 Revision。这样做不是为了增加概念,而是为了回答几个基本问题:当前响应由哪一版 Agent 产生,新版本发布后旧会话是否受影响,出现问题后能否回滚,历史会话能否按照当时配置解释和重放,以及两次行为差异究竟来自模型随机性还是配置已经改变。
如果这些问题无法回答,Eval 和 A/B 测试也很难建立可靠结论。Prompt 修改不只是编辑一段字符串,而应当进入版本、发布、灰度、观测和回滚流程。

Demo Agent 与生产级 Agent 的对比
Demo 证明 Agent 能运行;生产系统还要证明它可控、可查、可停、可恢复。
读完《Harness 其实就是控制论》,再回看 Blades 和自己对 Agent 系统的设计,我目前觉得:Harness 不是在模型外面随便包一层框架,也不等于 Eval 加重试。它真正做的是重新分配控制权。
模型适合负责理解模糊意图、生成候选方案、选择可能的工具、规划下一步和处理非结构化信息;确定性系统则负责管理状态、构建上下文、分配权限、执行副作用、记录事件、检测偏差、中止运行、控制版本和恢复现场。Blades 提供了事件驱动的 Agent Loop、工具运行时、Policy、Hook、Session、Compactor 和多 Agent Flow,让这些控制点有了统一的执行基础;应用层要做的,是在这些能力之上建立真正的生产控制面。
这可能也是 Agent 工程与传统软件工程一个很明显的差异:传统程序的确定性大多隐含在代码里,而 Agent 的不确定性迫使人把状态、边界、反馈和恢复机制全部显式地设计出来。
所以,我目前的理解是:
模型负责产生可能,Harness 负责决定哪些可能可以发生。
一个 Agent 能调用多少工具、能完成多复杂的任务,当然体现了它的能力;但一个系统能否解释它为什么运行、限制它可以做什么、发现它什么时候错了,并在失败后恢复,才真正决定它能不能进入生产。
Prompt Engineering、Context Engineering 和 Harness Engineering 也并不是彼此淘汰的三代技术。它们更像是视角不断向外扩展:从怎样表达意图,到怎样组织模型看到的信息,再到怎样让不确定的智能稳定地参与真实系统。
新名词还会继续出现,但工程面对的根本问题没有变。Harness 最朴素的含义,也许就是:不是让模型无所不能,而是让它的能力始终处于一个可观察、可约束、可纠偏、可恢复的系统之中。
参考资料
[1]
Harness 其实就是控制论: https://www.xiaohongshu.com/discovery/item/6a0ec5410000000038037228