← 回總覽

Agent 工程思考:从 ReAct 到 Agent Harness

📅 2026-07-22 20:00 vivo互联网技术 人工智能 7 分鐘 8198 字 評分: 88
人工智能 AI Agent 提示工程 RAG 模型评测与基准
📌 一句话摘要 文章阐述 Agent 工程从 ReAct 的模型循环转向通过 State Schema、运行事实与 UI 边界构建可交互、可恢复、可控制的 Agent Harness,实现事实可追溯、可恢复的产品能力。 📝 详细摘要 文章深入分析了传统 ReAct 循环仅关注模型的 Thought‑Action‑Observation 交互,不足以满足真实产品对状态恢复、用户审批、子 Agent 管理等工程需求。作者提出 Agent Harness 概念,通过前后端共享的 State Schema 明确事实边界,将运行时事件(如 tool.approval_required、artifac

作者:vivo 互联网项目团队- Ding Junjie 目录

  • Agent 不止是 model+loop
  • ReAct 的解释边界
  • 事实从哪里来
  • 从 Loop 到协议
  • State Schema 定义事实边界
  • UI 只能消费事实,不能补写事实
  • 系统要记得发生过什么
  • 最后
从 ReAct 出发,文章讨论 Agent 工程如何从“模型循环”走向 Harness:通过前后端共享的 State Schema、运行事实和 UI 边界,让模型行为变成可交互、可恢复、可控制、可追溯的产品能力。

1分钟看图掌握核心要点👇

!Image 1

!Image 2

大模型不是马,是大脑,而且是一颗刚刚觉醒的大脑。

_01_

Agent 不止是 model+loop

早期我理解的Agent 工程,可以被写成一段伪代码: whilenot done: reason act observe

用户输入一句话,模型思考一下,决定调用工具。工具返回结果,模型再思考,再决定下一步。

这也是 ReAct 的基本形态。Thought、Action、Observation 交替出现,模型在生成内容的过程中调用工具,再把工具结果放回下一步推理。

早期Agent概念刚出,我做了一个 demo ,这个循环完全够用了。命令行里输出 token,中间插入 tool call,再把 observation 塞回 prompt,最后模型给出结论。

但最近开始深入去做Agent 产品,过程中疯狂调研codex、lobehub、goose、opencode、PI、Flue等优秀Agent产品,发现远远不止这段伪代码所表达的。

真正的产品还会遇到一组运行时问题:

* 用户刷新页面之后,刚才的工具审批还在吗?

* 工具执行到一半,后端进程重启了,下一次从哪里恢复?

* 子 Agent 在后台跑,主线程要显示什么?

* 生成了一个 artifact,内容本身不塞进上下文,那它的引用、状态、归属在哪里?

* 用户点了 stop,哪些东西应该取消,哪些东西应该保留?

ReAct 不回答这些问题。

ReAct 解释的是模型怎么思考和行动。Agent 产品还需要一层工程系统:把模型做过的事,落成可恢复、可控制、可展示、可验证的软件事实。

下面我把这层系统叫做 Agent Harness。

_02_

ReAct 的解释边界

ReAct 的最小单元是: Thought -> Action -> Observation

这个抽象用来描述模型行为:模型先想,再行动,再观察结果。

到了工程系统里,单位会换成 event、state、checkpoint、control。

同样一次工具调用,放在真实的Agent产品工程里,大概会拆成这些事件: run.started message.created assistant.text.delta tool.call.created tool.approval_required tool.call.running tool.call.completed artifact.created run.finished

这里需要先区分 Observation 的身份。

在 ReAct 里,Observation 是给模型看的。工具返回了什么,就把这段结果塞回上下文,让模型继续想。这个层面上,它当然可以是一段文本。

但产品系统不能只停在这里。同一次工具调用,用户关心它还在不在跑,前端展示关心该不该显示审批按钮,存储层关心能不能恢复,artifact 面板关心这个结果属于哪次运行。这些消费方需要同一组可以被系统引用的事实。

如果 Observation 只剩文本,这些事实就没有权威来源。前端展示要从文本猜状态,后端要靠临时字段补状态,adapter 要把旧数据翻译成新 UI。问题会从运行时边界,转移到多处推导逻辑的一致性。

所以 ReAct 解释的是执行微循环:模型看到什么,下一步做什么。它没有定义产品系统里的事实边界:哪些事情已经发生,哪些状态可以恢复,哪些动作可以控制,哪些结果可以检查。

这里说的 Agent Harness,先落在一个具体职责上:为 Agent 产品定义事实协议。

!Image 3

_03_

事实从哪里来

最近看了很多开源高star的项目,会发现他们的整体设计都会解释一件事:模型做出的动作,怎么变成软件系统里的事实?

一种做法是让 ReAct loop 原样跑完,外面再写一层 UI adapter。它读 message,读 observation,读 tool result,尽量拼出 isBusy、pending-Approval、artifactRefs。

这种做法改动小:模型继续想,工具继续跑,前端也能先画出来。

问题是,这些事实只在事后出现。

等 adapter 看到 observation 时,审批可能只是一句话,artifact 可能只是模型提到过的一个路径,stop 也只剩一个按钮状态。系统还可以继续补字段、补判断、补同步逻辑,但这些逻辑都在追认已经发生过的事。

如果说,Agent = Harness + model ,那么Harness中最需要搞清楚的问题绝对不是skill怎么安装,mcp怎么加载,记忆是如何设计。而是更加工程的底气:事实应该在哪里产生。

当 tool call 需要审批,runtime 应该直接产生 tool.approval_required。当文件或文档被生成,runtime 应该写出 artifact.created 和可定位的 ref。用户点 stop,control 应该回到对应的 run,而不是让组件自己把按钮置灰。

所以 Harness 必须在运行路径上。tool call 开始、等待审批、执行完成、生成 artifact、写入 checkpoint,这些节点都应该由 runtime 产生 event 和 state。用户的 approve、stop、resume,也应该进入 runtime 的 control,而不是停在组件自己的状态里。

!Image 4

一个 Agent Harness 至少要回答这些问题: 现在谁在运行? 运行卡在哪里? 哪个状态可以恢复? 哪个动作需要用户审批? 哪个结果可以被检查? 哪个 artifact 属于哪次运行? 哪个子 Agent 是谁派出去的? 用户可以发送哪些命令? 刷新、重连、进程重启之后,系统如何回到同一个现场?

如果这些问题没有被 Harness 统一回答,实现里仍然要找地方放它们:前端组件、工具回调、消息渲染、数据库字段、临时缓存,或者某个“先这样”的判断。

判断标准可以直接写出来:同一个运行事实,不应该从多个来源拼出来。

pendingApproval、artifactRef、canStop 这类状态应该有明确归属。它们要么是 runtime state,要么是从 runtime state 派生的 view,不应该同时散在 message 文本、工具结果和前端本地状态里。

问题到这里,下一步就要设计系统怎么记、怎么算、怎么控制。

_04_

从 Loop 到协议

一个能产品化的 Agent 系统,数据流应该更像这样: runtime event -> agent.state -> agent.view -> UI user action -> agent.control -> runtime event

这里有三个词。

state 是事实。它应该由 runtime 和明确的业务边界生产。

比如: messages activeRun checkpoint pendingApproval todos subagents artifactRefs workspaceContext

这些状态影响任务能不能继续、能不能恢复、用户能不能检查结果。它们不属于前端展示缓存,而属于 Agent 的运行状态。

view 是派生。

比如: isBusy canStop waitingForFirstToken approvalBanner toolBadges subagentGroups messageProjection

这些东西应该从 state 算出来。activeRun 存在,所以可以显示 busy。pendingApproval 存在,所以显示审批条。run 已经开始但第一个 assistant part 还没有出现,所以显示 first-token waiting。

control 是命令。

比如: invoke(input, stateSnapshot) resume(approvalDecision) stop(runId) updateState(patch) reload(threadId)

control 只负责发命令,不顺手改展示状态。用户点批准,前端发 resume。审批条什么时候消失,取决于 runtime 是否继续执行并写回新的 state。UI 只从新的 state 派生出来。

边界在这里:runtime 负责写事实,UI 负责读事实。

Agent Harness 把这条边界固定成协议。

!Image 5

_05_

State Schema 定义事实边界

开发一个Agent时,不管是work Agent还是业务垂类Agent,都应该先设计好state schema ,只有这个清晰了,Agent产品的定位,工程的架构就清晰了。

如果先设计 prompt、tool、模型选择,最后才整理状态,很多运行事实会被迫挂在 message、工具结果或前端缓存上。只要这个 Agent 要进入用户工作流,就应该提前问: 哪些事实必须恢复? 哪些事实必须跨端一致? 哪些事实只是 view? 哪些事实是用户现场? 哪些事实可以从 messages 推导? 哪些事实必须成为一等状态?

审批在 UI 上是按钮,在 runtime 里是可恢复暂停点。

如果模型要执行一个危险命令,系统不能只在前端弹一个 modal。因为用户刷新页面之后,这个 modal 会丢。后端也不知道自己停在哪里。

可以把审批写成 state: agent.state.pendingApproval = { id, runId, turnId, toolCallId, toolName, arguments, policy }

用户点击批准: agent.control.resume({ approvalId, decision: "allow" })

runtime 从 checkpoint 继续,继续之后再写回新的 state。前端只消费 state。

artifact 的内容本体不一定要进 state。一个文档、一张图、一个大 JSON、一个外部系统连接,可能属于文件系统、数据库或对象存储。但 artifact 的引用、状态、归属应该进 state: artifactRef: id type title status ownerRunId createdByToolCallId contentRef

这样消息列表、右侧面板、历史记录、恢复流程都能通过同一个引用定位 artifact。内容可以懒加载,引用必须是事实。

子 Agent 也不应该只出现在文本里。

子 Agent 的运行关系也应该进入 state。如果主 Agent 只是输出一句: Started subagent abc123

前端想画子任务面板,就只能从文本里抠 ID。后续要做状态同步、跳转、恢复、错误展示时,这个 ID 仍然没有明确归属。

子 Agent 至少应该有运行事实: subagent: id parentRunId parentTurnId title status startedAt completedAt resultRef error

前端再从 subagent state 派生分组、徽标、进度文案、跳转目标。

state 的判断标准可以直接写成一句话:影响恢复、审批、继续执行、跨端一致、审计和可检查结果的东西,应该进入 state。

只改变展示方式的东西,留在 view。

!Image 6

_06_

UI 只能消费事实,不能补写事实

UI 可以负责渲染、交互、布局、流式展示和 view state。

runtime fact 不属于 UI。pendingApproval、artifactRef、activeRun 这类事实,应该由 runtime 写入 state。

UI 的位置在 state 下游:从 state 派生 view,再把 view 渲染成可操作界面。 agent.state.pendingApproval -> agent.view.approvalBanner -> UI: 批准 / 拒绝按钮

如果 runtime 没有写入 pendingApproval,UI 不应该从 message 文本创建这个事实: message: "Need approval to run shell command" -> UI 判断这句话像审批请求 -> UI 本地创建 pendingApproval -> UI 显示批准 / 拒绝按钮

这条路径的问题很具体:刷新之后这个 approval 还在吗?后端知道自己停在哪个 tool call 吗?用户点批准时,前端要把决定发给哪个 run?

边界规则是:runtime 写入 fact,UI 消费 fact。

!Image 7

_07_

系统要记得发生过什么

到这里,Harness 的含义可以再落一下。

它不是给 UI 多加一层封装,而是把 Agent 运行中的关键事实放进系统协议里:模型发起了哪个 tool call,当前卡在哪个审批,哪个 run 产生了 artifact,用户的 stop 要终止哪次运行。

Agent 的运行不会总是顺着一条完整的同步调用走完。页面会刷新,进程会重启,工具调用会等待审批,用户可能点 stop,子 Agent 也可能在另一个执行上下文里结束。

这时问题就不是 UI 怎么画,而是这些事实有没有稳定归属,能不能被恢复、继续和追溯。

可以把这个要求写成可检查的行为: 刷新页面后,pendingApproval 仍然存在且指向同一个 toolCall 进程重启后,能从 checkpoint resume 到同一个 run/turn 用户 stop 后,activeRun 终止且后端不会继续执行后续 tool call artifactRef 存在时,内容可被定位、可追溯到 ownerRunId / toolCallId 子 Agent 完成后,parent turn 能拿到 resultRef 并展示归属

这组检查最后落到同一个问题:运行事实有没有明确归属。pendingApproval、artifactRef、activeRun 这些东西,必须由 runtime 生成并持久化;UI 只能从 state 派生 view,不能替系统补事实。

所以 ReAct 和 Harness 的分工也会变得清楚:

ReAct 解释模型怎样一步步决定下一步。

Harness 保证这些步骤在软件系统里发生过、能恢复、可控制、可审计。

_08_

最 后

Agent 产品的难点,不止是写出一个会调用工具的循环。

那个循环让模型“能动”,但用户真正依赖的是系统层面的确定性:刷新不丢现场、重启能恢复、该停就停、结果可定位、责任可追溯。

这就是 Agent Harness 的价值:把一次次“模型行为”落成一组可引用、可恢复、可控制的软件事实。

一句话总结:ReAct 让模型动起来;Harness 让这件事在产品里可被信任。

_END_

猜你喜欢

* AI编码实战 | 0 行手写代码,2 天重构 2 万行 Vue 项目

* 当 AI 进入推荐系统:从“推什么”到“怎么选”

* 未来,什么才是 AI“正确的使用方式”

查看原文 → 發佈: 2026-07-22 20:00:00 收錄: 2026-07-23 02:00:45

🤖 問 AI

針對這篇文章提問,AI 會根據文章內容回答。按 Ctrl+Enter 送出。