Agent Harness 的工程结构:控制权放在哪里
一个退款 Agent 拿到订单号后,模型会自然地倾向于调用 write_refund。真正决定它能不能退款的,不该是模型那句“我已经确认过了”,而应是订单状态、审批记录和业务政策。
这就是选 Agent 框架时最容易漏掉的一层。模型、工具、Memory、Multi-Agent 的功能表当然重要,但更早要问:下一步由谁决定,状态由谁保存,失败以后谁负责。
同一个退款任务,可以把控制交给模型、普通代码、状态图或持久化运行时。四种实现都能跑通 Demo;一旦遇到审批、暂停、重试或崩溃,差异就会立刻暴露。
先看结论: Harness 不是“给模型套一层 SDK”。它要把开放判断、确定约束、外部副作用和长时恢复分别交给能承担责任的部分。
本文用一个会被审批 Gate 拦住的退款动作,带你从决策、控制和执行三层看清这件事。
一、先把责任从 Prompt 里拿出来
Section titled “一、先把责任从 Prompt 里拿出来”为了看清 Harness,可以先分成三个平面。
Decision Plane
Section titled “Decision Plane”模型理解目标、选择工具、生成参数、提出计划或 Handoff。
它擅长处理开放语言和不确定判断,不适合单独承担权限、事务和完成证明。
Control Plane
Section titled “Control Plane”程序、图或策略引擎决定哪些动作可达、是否需要审批、预算是否允许、错误怎样恢复、什么时候停止。
控制平面承接业务责任。
Execution Plane
Section titled “Execution Plane”工具、浏览器、终端、数据库、队列和沙箱真正改变环境。
执行平面必须返回结构化 Observation,并处理超时、幂等和资源隔离。
很多 Agent 的失控不是模型“不会推理”,而是三个平面被压进了一段 Prompt:模型既提议动作,又解释权限,还根据自己的解释宣布成功。把它们拆开,排错时才知道该查模型、政策还是工具终态。
二、搭一个 Harness,七块东西各管一件事
Section titled “二、搭一个 Harness,七块东西各管一件事”第 2 讲从职责解释 Harness,这一讲从工程模块重新组织。
1. Model Gateway
Section titled “1. Model Gateway”统一模型调用、认证、重试、流式输出、Token 统计和能力差异。网关可以提供共同接口,但不能抹掉 Tool Call、结构化输出和缓存语义差异。
2. Context Builder
Section titled “2. Context Builder”从目标、状态、Memory、检索和工具目录构建当前输入。它还负责预算、压缩、来源和不可信内容隔离。
3. Agent Runner
Section titled “3. Agent Runner”执行 Agent Loop:调用模型、解析候选动作、调度工具、回填 Observation、处理 Handoff 和终态。
4. Tool Runtime
Section titled “4. Tool Runtime”验证参数、检查授权、执行动作、整形结果,并管理超时、并发、幂等和副作用。
5. State and Session
Section titled “5. State and Session”保存会话、任务状态、Checkpoint、工件和审批。消息历史只是其中一类数据。
6. Policy and Guardrails
Section titled “6. Policy and Guardrails”执行最小权限、路径/域名白名单、内容过滤、预算、人工批准和取消。
7. Telemetry
Section titled “7. Telemetry”记录 Trace、Span、事件、错误、成本和完成证据,为 Evaluation 和故障诊断提供数据。
模块可以合并在一个进程里,也可以拆成服务。不要为了画架构图而拆服务;分层的实际价值是出故障时能定位责任,例如“模型提了错误候选”与“Policy 放行了不该放的动作”是两种完全不同的问题。
三、四种控制模型,先看谁能让事情发生
Section titled “三、四种控制模型,先看谁能让事情发生”Model-driven:模型决定大部分下一步
Section titled “Model-driven:模型决定大部分下一步”这种 Harness 提供工具和循环,让模型动态规划。
优势是代码少、适应开放任务快;风险是行为变化大,重复和误用工具更难提前发现。smolagents、Strands 的典型用法接近这条路线。[1][2]
适合探索、研究和低风险任务。进入写操作前,需要独立 Policy Gate。
Code-driven:普通代码拥有流程
Section titled “Code-driven:普通代码拥有流程”应用用 if、函数和异常处理组织模型与工具。OpenAI Agents SDK、PydanticAI 都强调与普通 Python 组合。[3][4]
优势是团队熟悉、单元测试容易、控制明确;复杂分支和长时恢复则需要自行组织。它适合把“必须如此”的规则写成可测试的条件,而不是藏进角色提示词。
先看图中的原语数量,再看自己需要的控制点。SDK 能提供足够少的原语,不代表应用就可以省掉授权和状态归属。

图 1:OpenAI Agents SDK 代表少量原语与普通代码编排的路线。来源见文末。
Graph-driven:状态和边拥有可达路径
Section titled “Graph-driven:状态和边拥有可达路径”LangGraph、Microsoft Agent Framework Workflow 把状态、节点和边显式化。[5][6]
模型在节点内做开放判断,图决定下一步可以去哪里。它适合审批、循环、恢复和复杂分支,代价是图设计与迁移成本。
图里最值得看的不是节点数量,而是写操作前有没有一条不能被模型绕开的边。

图 2:LangGraph 把 State、Node、Edge 和 Checkpoint 置于核心。来源见文末。
Runtime-driven:持久化运行时拥有生命周期
Section titled “Runtime-driven:持久化运行时拥有生命周期”Temporal、DBOS 等系统把 Workflow 历史、Activity 重试、定时器和恢复变成运行时能力。[7][8]
它们不决定模型该说什么,却决定进程崩溃以后任务怎样继续。适合跨小时、跨天和有副作用的流程。
实际生产系统通常混合四者:Graph 规定大阶段,Code 执行确定逻辑,Model 处理开放节点,Durable Runtime 保存生命周期。
四、别让五个地方同时声称自己是真相
Section titled “四、别让五个地方同时声称自己是真相”状态分散是 Harness 最难排查的问题之一。
同一个任务可能同时存在:
- 模型消息历史中的“当前计划”;
- 应用数据库中的任务状态;
- LangGraph Checkpoint;
- Memory Service 中的用户偏好;
- Durable Runtime 的 Workflow 历史;
- 外部业务系统的真实终态。
它们不能都声称自己是权威。
建议按问题分配:
| 状态 | 权威位置 |
|---|---|
| 订单是否退款 | 订单/支付系统 |
| Workflow 运行到哪 | Durable Runtime 或 Checkpoint Store |
| 当前计划和中间工件 | 任务状态 |
| 用户长期偏好 | Memory Store |
| 模型看到的最近对话 | Session History |
模型上下文应该从权威状态生成,而不是反过来用模型文本覆盖权威状态。模型说“订单已退款”只能是候选解释;支付系统或订单系统的终态才是事实。
五、Hooks 与 Middleware 为什么重要
Section titled “五、Hooks 与 Middleware 为什么重要”框架常提供 Hook、Middleware、Interceptor 或 Event Handler。它们允许团队在不修改 Agent 逻辑的情况下加入:
- 调用前权限检查;
- 工具参数脱敏;
- 预算计数;
- Trace 属性;
- 模型路由;
- 结果缓存;
- 审批和取消;
- 错误分类。
这类扩展点是 Harness 的控制面。设计时要避免两个极端:所有逻辑塞进 Hook,导致执行顺序不透明;或者完全没有 Hook,只能修改框架内部。
好扩展点应明确时机、输入、是否可修改、错误传播和幂等要求。
六、OpenHarness 为什么值得看,又不能照单全收
Section titled “六、OpenHarness 为什么值得看,又不能照单全收”OpenHarness 等项目尝试把 Toolkit、Memory、Governance、Swarm 和生命周期集中呈现,适合观察一个“完整 Harness”可能包含什么。[9]

图 3:OpenHarness 展示了较完整的能力地图。能力面广不等于每个应用都应启用全部模块。
读这类项目时要分三层:
- 核心运行时哪些是必需的;
- 哪些是可选能力;
- 哪些依赖托管服务或特定基础设施。
完整架构适合学习边界,不适合成为每个项目的默认起点。
七、把开放判断和确定约束放在不同位置
Section titled “七、把开放判断和确定约束放在不同位置”可以采用一条简单原则:
开放、可逆、低风险 -> 模型拥有更多选择确定、不可逆、高风险 -> 代码/图/人工拥有控制跨时间、需恢复 -> 持久化运行时拥有生命周期例如客服 Agent:
- 模型判断用户意图;
- 代码验证身份和金额;
- 图规定退款前必须经过政策检查和审批;
- Durable Runtime 等待人工回复;
- 支付系统保存最终交易事实。
这不是削弱 Agent,而是把不同能力放回适合的位置。
八、动手跑一次:同一个写操作,两种控制位置
Section titled “八、动手跑一次:同一个写操作,两种控制位置”从本讲目录执行:
python3 experiments/control_placement.pycontrol_placement.py 模拟模型提出 write_refund。先不要把它理解成退款系统,而把它当成一个最小问题:写操作到底在什么条件下才能从“候选”变成“执行”。
第一条路径让模型直接提出写动作,但 Policy 发现没有审批,终态是:
{"terminal": "blocked", "reason": "approval_required"}第二条路径由状态图先读取订单、经过审批节点,再执行写入,终态为 success。
完整结果见 experiment-result.json。重点核对两条终态:没有审批的候选被 blocked;经过 read_order -> request_approval -> write_refund 的路径才是 success。
实验没有比较谁“更聪明”。它只说明:同一个候选动作,放在不同控制结构里,会得到不同的安全语义。
九、选择框架前的十二个问题
Section titled “九、选择框架前的十二个问题”- 谁拥有 Agent Loop?
- 下一步由模型、代码还是图决定?
- 业务状态保存在哪里?
- 是否支持结构化 Checkpoint?
- 工具授权在哪里执行?
- 有副作用动作怎样幂等?
- 是否支持暂停、取消和人工审批?
- 进程重启后能否恢复?
- Trace 能否跨模型、工具和 Handoff?
- 是否能在框架外使用普通测试工具?
- 托管功能与开源核心怎样划分?
- API 变化时,状态和 Workflow 怎样迁移?
如果这些问题没有答案,“支持多少 Agent 模式”并不是最重要的。
十、收束:框架不是控制权的替身
Section titled “十、收束:框架不是控制权的替身”Harness 设计不是寻找一个包办全部责任的框架,而是把控制权、状态和失败责任写清楚。功能越多的框架,越需要把“谁能让事情发生”问得更具体。
模型负责开放判断,代码和图保留确定约束,持久化运行时承接时间,业务系统保存事实。把这四句话落实到一个真实写操作上,框架选型才有可比较的标准。
下一讲深入 Graph 和 State Machine:怎样把 Agent 的不确定性放进显式状态,让循环能验证、有界并且可恢复。
延伸阅读:从选型表回到控制问题
Section titled “延伸阅读:从选型表回到控制问题”- 想对照“少原语 + 普通代码”的路线:读 OpenAI Agents SDK 与 PydanticAI。
- 想看显式状态、边和恢复如何落地:读 LangGraph 与 Microsoft Agent Framework。
- 想处理跨小时或跨天的业务流程:读 Temporal Python SDK 和 DBOS Transact Python。
[1] Hugging Face, smolagents. https://github.com/huggingface/smolagents
[2] Strands Agents. https://github.com/strands-agents/harness-sdk
[3] OpenAI Agents SDK. https://github.com/openai/openai-agents-python
[4] PydanticAI. https://github.com/pydantic/pydantic-ai
[5] LangGraph. https://github.com/langchain-ai/langgraph
[6] Microsoft Agent Framework. https://github.com/microsoft/agent-framework
[7] Temporal Python SDK. https://github.com/temporalio/sdk-python
[8] DBOS Transact Python. https://github.com/dbos-inc/dbos-transact-py
[9] OpenHarness. https://github.com/HKUDS/OpenHarness