6632 字
33 分钟

Agent Harness 架构设计:从大 Prompt 到可测试的模块化运行时

本文是 精英 Agent 工程师学习路线:从范式理解到生产级落地 阶段 3 的配套学习笔记。 核心结论:模型只是 Harness 中一个可替换的决策组件。真正决定系统能否生产运行的,是状态、事件、工具执行、恢复、安全、人工接管、评估和观测如何围绕模型形成闭环。


0. 学习目标、范围与非目标#

学完本文后,你应该能够:

  1. 解释 Model、Agent、Workflow、Harness、Runtime 与 Control Plane 的边界;
  2. 把任意 Agent 业务拆成输入、状态、上下文、决策、动作、验证和证据模块;
  3. 定义一次 Run、一次 Step、一次 Attempt 与一次 Tool Call 的生命周期;
  4. 明确每个模块的输入、输出、失败语义、超时、幂等和测试方式;
  5. 设计能够 Checkpoint、暂停、恢复、取消和人工接管的状态机;
  6. 避免让对话消息同时承担业务状态、执行日志与长期记忆;
  7. 用统一事件信封连接 API、编排器、工具、Trace、Eval 与前端流式展示;
  8. OrderFlow-Agent 画出可落地的 Harness 架构并写出核心接口。

本文只定义 Harness 的系统边界,不提前重复后续专篇:

后续阶段本文只讲后续专篇深入
阶段 4 工具/MCP/SkillsTool Registry 与 Executor 接口协议、Schema、授权、供应链
阶段 5 MemoryContext Port 与 Memory Port写入策略、冲突、删除、评估
阶段 6 RAGEvidence Retriever 接口Hybrid、GraphRAG、RuleRAG
阶段 7 PlanningPlanner/Verifier 生命周期规划、修复、Reflection 算法
阶段 10 安全Policy Decision Point威胁模型与纵深防御
阶段 11 评估Trace/Eval Hook数据集、指标与归因

1. Harness 到底是什么#

1.1 六个容易混淆的概念#

概念核心职责不负责什么
Model根据上下文生成 token 或结构化决策事务、权限、状态真相
Agent围绕目标观察、决策、行动天然不保证可靠与安全
Workflow预定义节点、条件和状态迁移不一定包含动态模型决策
Harness给 Agent 提供上下文、工具、状态、安全、恢复、评估的控制外壳不等于某个 Prompt
Runtime实际调度 Run、Step、事件和资源不等于管理后台
Control Plane管配置、版本、策略、发布、权限、配额不直接执行每一步业务动作

可以把 Harness 看成 Agent 的操作系统边界:

Task / Goal
Harness
├── Context
├── State
├── Planner / Router
├── Model Gateway
├── Tool Runtime
├── Guardrails / Policy
├── Checkpoint / Recovery
├── HITL
└── Trace / Eval
Verified Outcome

OpenAI Agents SDK 公开的 Runner loop、tools、handoffs、guardrails、sessions 与 tracing,Google ADK 的 Runner/Event/Session/State/Artifact,LangGraph 的 state graph、durable execution、persistence 与 interrupt,以及 Microsoft AutoGen Core 的 message-passing runtime,都可以视为不同形态的 Harness 公开实现。[S1] [S2] [S3] [S4]

1.2 Harness 不是“把所有能力塞给模型”#

错误理解:

System Prompt
+ 所有历史消息
+ 所有工具描述
+ 所有文档
+ 所有业务规则
= Agent

正确理解:

Harness 先根据身份、状态和任务选择最小能力集合,
再构建最小充分上下文,
让模型只在被授权的候选动作内做局部决策,
最后由确定性系统执行、验证和记录结果。

Anthropic 的公开工程文章强调从最简单可行方案开始,并把 workflow 与 agent 分开;这不是反对 Agent,而是提醒“动态性必须对应真实收益”。[S5]


2. 三平面、四循环与五种状态#

2.1 三个系统平面#

Control Plane
Agent/Prompt/Tool/Policy 版本、模型路由、权限、灰度、预算
Data & Execution Plane
Run 调度、节点执行、模型调用、工具调用、Checkpoint、队列
Evidence Plane
Trace、Audit、Evaluation、Failure Sample、Replay、Cost

三个平面应通过不可歧义的版本与 ID 关联:

{
"run_id": "run_01J...",
"agent_version": "orderflow@3.2.0",
"workflow_version": "refund@7",
"prompt_bundle_version": "2026-07-15.2",
"tool_registry_version": "42",
"policy_bundle_version": "refund-cn@18",
"model_route_version": "support-default@9"
}

2.2 四个闭环#

任务闭环:理解 -> 决策 -> 行动 -> 验证 -> 交付
状态闭环:读取 -> 条件迁移 -> 持久化 -> 恢复
安全闭环:识别主体 -> 授权 -> 策略 -> 审计
改进闭环:观察 -> 归因 -> 数据集 -> 评估 -> 发布

只有第一个闭环的系统是 Demo;生产 Harness 必须同时承载四个闭环。

2.3 五种不同状态#

状态例子推荐载体
对话状态用户刚补充了订单号Message history / summary
任务状态当前在 verify_eligibilityTyped AgentState
业务状态订单已退款订单/退款服务真相源
执行状态工具 Attempt 2 超时Run/Step/Attempt records
学习状态此失败已进入回归集Eval dataset / failure store

最危险的设计是把五类状态都藏在消息列表里,因为消息无法提供事务约束、并发控制、结构化查询和可靠恢复。


3. 一次 Agent Run 的完整生命周期#

01 Receive Request
02 Authenticate & Authorize Entry
03 Normalize Input
04 Create Run + Pin Versions
05 Load Checkpoint / Initialize State
06 Build Minimal Context
07 Route or Plan Next Step
08 Call Model or Deterministic Node
09 Validate Proposed Action
10 Execute Tool with Policy
11 Persist Observation + State Transition
12 Verify Goal / Business State
13 Continue, Pause, Handoff, or Terminate
14 Emit Response + Trace + Eval Hooks

3.1 Run、Step、Attempt、Call 不可混用#

Run
└── Step: query_order
├── Attempt 1
│ └── Tool Call: timeout
└── Attempt 2
└── Tool Call: success
层级ID作用
Requestrequest_idHTTP/API 去重与客户端关联
Runrun_id一次目标执行生命周期
Threadthread_id多次 Run 的会话/任务范围
Stepstep_id逻辑节点执行
Attemptattempt_idStep 的某次尝试
Model Callmodel_call_id一次模型请求
Tool Calltool_call_id一次工具执行请求
Tracetrace_id跨服务观测关联

重试产生新 attempt_id,但保留同一 step_id;恢复产生新的 Worker lease,但通常仍属于同一 Run。

3.2 状态机#

created
-> queued
-> running
-> awaiting_input
-> awaiting_confirmation
-> awaiting_human
-> retry_scheduled
-> succeeded
-> failed
-> cancelled
-> budget_exhausted

状态迁移必须由代码校验,而不是让模型自由输出任意状态字符串。


4. Harness 模块总表#

模块输入输出关键风险主要测试
Request GatewayHTTP/eventauthenticated request重放、伪造、超载auth、rate limit
Input Normalizerraw inputcanonical request丢语义、编码攻击边界/模糊测试
Intent/Slotrequest + historytyped intent错误路由、漏槽位accuracy、slot F1
Context Builderstate + sourcescontext package污染、超预算、泄露precision、ACL
Capability Routerintent + policycandidate capabilities能力过曝、漏召回routing recall
Plannergoal + stateplan/next action循环、过时计划validity、efficiency
Model Gatewaymodel requestnormalized events限流、格式漂移adapter contract
Tool Registryidentity + stateallowed tool specs描述投毒、版本漂移registry snapshot
Tool Executorvalidated callobservation越权、重复副作用policy/idempotency
State Storetransitioncheckpoint并发覆盖、无法恢复CAS/replay
Verifierstate + evidencepass/fail/repair自我确认偏差verifier recall
Guardrails/Policysubject/action/resourceallow/deny/obligationsfail-openadversarial tests
HITLreview packagehuman decision信息不足、重复执行handoff precision
Response Composerverified stateuser response过度承诺、泄露faithfulness
Trace/Evalevents + outcomeevidence/metricsPII、不可复现schema/completeness

关键设计要求:每个模块都必须有明确失败输出,而不是只定义成功返回值。


5. AgentState:恢复协议,不是随手字典#

5.1 Typed State#

from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
class BudgetState(BaseModel):
steps_used: int = 0
model_calls_used: int = 0
tool_calls_used: int = 0
input_tokens_used: int = 0
output_tokens_used: int = 0
cost_usd: float = 0.0
class AgentState(BaseModel):
schema_version: Literal["2"] = "2"
run_id: str
thread_id: str
tenant_id: str
user_id: str
status: Literal[
"created",
"queued",
"running",
"awaiting_input",
"awaiting_confirmation",
"awaiting_human",
"succeeded",
"failed",
"cancelled",
"budget_exhausted",
]
current_node: str
intent: str | None = None
slots: dict[str, str] = Field(default_factory=dict)
fact_refs: list[str] = Field(default_factory=list)
evidence_refs: list[str] = Field(default_factory=list)
pending_action_ref: str | None = None
completed_steps: list[str] = Field(default_factory=list)
budget: BudgetState = Field(default_factory=BudgetState)
state_version: int = 0
updated_at: datetime

5.2 State 中应放什么#

判断规则:

进程重启后恢复下一步是否必需?
是 -> State 或稳定引用
否 -> Trace/临时缓存
它是否是业务真相?
是 -> 业务系统保存,State 只放 ID + version/hash

不应直接塞入 State:

  • 完整 PDF 或网页;
  • 大段模型推理文本;
  • API Key 与支付敏感数据;
  • 可从真相源重新读取的巨大工具结果;
  • 未经筛选的全部历史消息。

5.3 状态迁移契约#

class StateTransition(BaseModel):
transition_id: str
run_id: str
from_version: int
from_status: str
to_status: str
node: str
event_ref: str
patch: list[dict]
occurred_at: datetime

写入时使用 Compare-And-Swap:

UPDATE agent_runs
SET state = :new_state,
state_version = state_version + 1
WHERE run_id = :run_id
AND state_version = :expected_version;

受影响行数为 0 时进入冲突处理,不能 last-write-wins。


6. Context Builder:决定模型“看什么”#

Context Builder 是 Harness 中最容易被低估的模块。它不是字符串拼接,而是受预算、权限、来源和新鲜度约束的选择器。

6.1 Context Package#

class ContextItem(BaseModel):
item_id: str
kind: Literal[
"instruction",
"task_state",
"conversation",
"tool_fact",
"retrieved_evidence",
"memory",
]
trust: Literal["system", "verified", "untrusted"]
source_ref: str
content: str
token_estimate: int
effective_at: datetime | None = None
expires_at: datetime | None = None
class ContextPackage(BaseModel):
package_id: str
policy_version: str
items: list[ContextItem]
total_token_estimate: int
omitted_item_refs: list[str]

6.2 构建顺序#

1. 固定系统与业务边界
2. 当前目标和结构化状态
3. 已验证的最新工具事实
4. 与当前节点相关的规则证据
5. 最少必要对话片段/摘要
6. 经过写入策略治理的记忆
7. 保留输出与工具调用预算

“Lost in the Middle” 显示长上下文中信息位置会影响使用效果;这类研究支持一个工程结论:上下文窗口变大不等于 Context Builder 可以消失。[P1]

6.3 测试 Context,而不是只看最终回答#

指标至少包括:

Context Precision
Required Fact Recall
Stale Context Rate
Unauthorized Context Rate
Instruction/Data Separation Pass Rate
Tokens per Successful Step

7. Router、Planner 与 Executor 的权力分离#

7.1 三者职责#

Router:选择进入哪个能力域/子图
Planner:提出完成目标的步骤或下一动作
Executor:在策略、权限和状态约束下执行动作

模型可以参与 Router 与 Planner,但不应绕过 Executor。

7.2 Capability Router 输出候选集#

{
"route": "refund_workflow",
"candidate_capabilities": [
"order.read",
"logistics.read",
"policy.search.refund"
],
"excluded_capabilities": [
"refund.create",
"compensation.create"
],
"reason_code": "facts_not_verified"
}

开始阶段不暴露写工具;只有事实、资格与确认完成后,Registry 才为该 Step 生成短生命周期的写能力集合。

7.3 Plan 是数据,不是散文#

class PlanStep(BaseModel):
step_id: str
objective: str
capability: str
dependencies: list[str]
success_condition: str
failure_route: str
max_attempts: int = 1
class Plan(BaseModel):
plan_id: str
goal: str
assumptions: list[str]
steps: list[PlanStep]
replan_conditions: list[str]

Plan-and-Solve、ReAct、Reflexion 等研究提供了规划、行动与反馈的不同组织方式;Harness 的职责是把这些策略装入同一预算、状态和验证协议,而不是押注唯一推理范式。[P2] [P3] [P4]


8. Model Gateway:将概率组件标准化#

8.1 请求信封#

class ModelRequest(BaseModel):
model_call_id: str
run_id: str
step_id: str
purpose: Literal[
"route",
"extract",
"plan",
"decide",
"verify",
"respond",
]
context_package_ref: str
tool_registry_snapshot_ref: str | None
output_schema: dict | None
timeout_seconds: float
model_route: str

8.2 归一化事件#

model.started
model.text.delta
model.tool_call.delta
model.output.completed
model.usage.completed
model.blocked
model.failed

8.3 Provider Adapter 契约测试#

各厂商对流式工具参数、usage 到达时机、结束原因、拒绝与安全阻断的表示不同。适配器测试要固定原始 fixture,检查:

原始事件是否丢失
工具参数增量能否正确组装
终止原因是否归一化
token/cost 是否只计一次
取消是否真正传播
未知事件是否 fail-closed 或可观测

OpenAI Responses/Agents、Anthropic tool use、Gemini function calling 都可以接入同一内部协议,但不要假设它们的事件和错误语义完全相同。[S6]


9. Tool Runtime:动作边界必须确定性#

9.1 工具选择与执行分开#

LLM output: ToolProposal
↓ parse + schema
Harness: ActionRequest
↓ authz + policy + confirmation
Executor: ToolCommand
↓ adapter
Remote System
↓ normalize + verify
Harness: Observation

9.2 Observation Schema#

class Observation(BaseModel):
tool_call_id: str
tool_name: str
tool_version: str
status: Literal[
"succeeded",
"rejected",
"failed_retryable",
"failed_terminal",
"outcome_unknown",
]
result_ref: str | None
error_code: str | None
fact_version: str | None
started_at: datetime
finished_at: datetime

outcome_unknown 是关键状态:写请求可能已经被远端受理,只是响应丢失。Harness 应先调用查询/对账工具,不可直接重复写。

9.3 工具循环终止#

ReAct 证明交替生成 reasoning/action 与读取 observation 能增强交互任务能力;Toolformer、Gorilla、ToolLLM 则探索模型学习工具使用和大规模 API 调用。[P2] [P5] [P6] [P7] Harness 仍必须提供:

max_steps
max_tool_calls
per_tool_limit
duplicate_call_detector
no_progress_detector
wall_clock_deadline
cost_budget
terminal_state_checker

10. Guardrails、Policy 与 HITL#

10.1 Guardrail 不是一个输出过滤器#

Input Guardrail
检测输入风险,但不承担全部授权
Context Guardrail
ACL、来源、敏感数据、指令/数据隔离
Action Guardrail
主体、资源、权限、金额、频率、确认
Output Guardrail
忠实性、隐私、格式、承诺边界
Runtime Guardrail
步数、成本、超时、循环、沙箱

OpenAI Agents SDK 把 input/output guardrail 与 tool guardrail 暴露为不同执行点;NIST AI RMF、OWASP LLM/Agent 安全资料则从风险治理和应用威胁角度要求生命周期控制。[S7]

10.2 Policy Decision#

class PolicyDecision(BaseModel):
decision: Literal["allow", "deny", "require_confirmation", "require_human"]
policy_version: str
reason_codes: list[str]
obligations: list[str]
expires_at: datetime | None

obligations 可能是:

mask.phone
limit.refund_amount=5000
log.audit.high_risk
verify.order_owner
confirm.user.v2

10.3 人工接管包#

人工不能只看到“模型不确定,请判断”。Review Package 应包含:

{
"run_id": "run_01J...",
"decision_type": "refund_over_threshold",
"request_summary": "订单超过72小时未发货,用户申请全额退款",
"verified_facts": ["fact://order/123@v8", "fact://logistics/123@v3"],
"policy_evidence": ["policy://refund/v18#R001"],
"proposed_action": "action://run_01J/refund-1",
"risk_reasons": ["amount_over_auto_limit"],
"allowed_decisions": ["approve", "reject", "request_more_info"],
"expires_at": "2026-07-15T12:30:00Z"
}

人工批准后必须重新校验资源状态,因为等待期间订单可能已经变化。


11. Checkpoint、暂停与恢复#

11.1 Durable Execution 的工程含义#

LangGraph 的 durable execution 和 persistence、Temporal 的 durable execution 都强调:流程进度持久化后,进程故障不应迫使任务从头开始。[S3] [S8]

Agent 场景需要持久化:

Pinned Versions
Current Typed State
Completed Step IDs
Pending External Operation
Retry Schedule
Human/Confirmation Token
Budget Usage
Last Durable Event Sequence

11.2 Checkpoint 时机#

推荐至少在以下边界保存:

Run 创建后
每个有副作用动作前
每个工具结果归一化后
状态迁移后
暂停/人工接管前
最终响应前

11.3 “Exactly Once” 幻觉#

跨网络无法靠一句“exactly once”消除不确定性。实际组合是:

at-least-once delivery
+ idempotent consumer
+ idempotency key
+ conditional state transition
+ outbox/inbox
+ final-state reconciliation
= effectively-once business outcome

11.4 恢复决策#

class RecoveryDecision(BaseModel):
action: Literal[
"resume_next_step",
"retry_attempt",
"reconcile_external_state",
"await_human",
"fail_terminal",
]
reason: str
checkpoint_version: int

恢复前必须验证:Workflow/Tool/State Schema 版本是否兼容。不能用新代码静默解释旧 Checkpoint。


12. 统一事件协议#

12.1 Event Envelope#

from typing import Any
class AgentEvent(BaseModel):
schema_version: Literal["1"] = "1"
event_id: str
trace_id: str
run_id: str
thread_id: str
step_id: str | None
attempt_id: str | None
sequence: int
type: str
occurred_at: datetime
producer: str
data: dict[str, Any]
data_classification: Literal["public", "internal", "confidential", "restricted"]

12.2 事件分类#

run.created / run.started / run.paused / run.completed / run.failed
step.started / step.completed / step.failed
model.started / model.completed / model.failed
tool.proposed / tool.authorized / tool.completed / tool.failed
state.transitioned / checkpoint.saved
human.requested / human.decided
budget.warning / budget.exhausted
eval.queued / eval.completed

12.3 三类消费方#

消费方需要什么不应看到什么
前端安全进度、可取消状态、最终结果内部 Prompt、敏感工具返回
运维Span、错误、延迟、版本、资源无必要的用户 PII
审计/评估动作证据、策略决策、结果引用未授权跨租户数据

一个内部事件不应未经投影直接发给浏览器。

OpenTelemetry GenAI semantic conventions 为模型、Agent 与工具操作提供跨实现的观测语义;Harness 可以在内部保持更丰富事件,再映射到 OTel Span。该规范已迁入独立仓库,当前仍标记为 Development,因此内部事件协议不能绑定未经版本固定的属性名。[S9]


13. OrderFlow-Agent Harness 架构#

13.1 组件图#

Client
API Gateway / Identity / Rate Limit
Run Service ───────────────→ Event Stream
↓ ↑
StateGraph Runtime ────────→ Checkpoint Store
├── Input + Intent
├── Context Builder ─────→ Memory/RAG Ports
├── Capability Router
├── Planner / Decision
├── Policy Decision Point
├── Tool Executor ───────→ Order/Logistics/Refund Services
├── Verifier
├── HITL Gateway
└── Response Composer
Trace + Audit + Eval + Failure Collector

13.2 退款主链路#

normalize_input
-> identify_intent
-> collect_required_slots
-> query_order
-> verify_order_ownership
-> query_logistics
-> retrieve_effective_policy
-> decide_eligibility
-> verify_decision
-> request_confirmation?
-> create_refund
-> reconcile_refund_state
-> compose_response

13.3 节点契约#

from typing import Protocol
class NodeContext(BaseModel):
run_id: str
step_id: str
attempt_id: str
deadline_at: datetime
cancellation_requested: bool
class NodeResult(BaseModel):
outcome: Literal[
"completed",
"retry",
"pause_for_input",
"pause_for_confirmation",
"pause_for_human",
"failed",
]
state_patch: list[dict] = Field(default_factory=list)
emitted_event_refs: list[str] = Field(default_factory=list)
next_node: str | None = None
retry_after_seconds: float | None = None
class AgentNode(Protocol):
async def run(
self,
state: AgentState,
context: NodeContext,
) -> NodeResult: ...

13.4 节点不能做的事#

  • 直接修改共享 State 对象;
  • 绕过 Tool Executor 调远端写 API;
  • 自己吞掉异常并伪造成功;
  • 使用未固定版本的 Prompt/Tool;
  • 把敏感原文写进普通事件;
  • 无预算递归调用自身;
  • 在没有 Checkpoint 的情况下等待人工数小时。

14. 最小 Runtime 骨架#

class AgentRuntime:
def __init__(
self,
graph: "CompiledGraph",
state_store: "StateStore",
event_bus: "EventBus",
policy: "PolicyEngine",
clock: "Clock",
) -> None:
self.graph = graph
self.state_store = state_store
self.event_bus = event_bus
self.policy = policy
self.clock = clock
async def advance(self, run_id: str) -> None:
lease = await self.state_store.acquire_lease(run_id)
async with lease:
state = await self.state_store.load(run_id)
self._assert_runnable(state)
while state.status == "running":
self._assert_budget(state)
node = self.graph.resolve(state.current_node)
context = self._new_node_context(state)
await self.event_bus.emit(step_started(state, context))
result = await node.run(state.model_copy(deep=True), context)
next_state = apply_validated_patch(state, result.state_patch)
next_state = apply_outcome(next_state, result)
state = await self.state_store.compare_and_swap(
expected_version=state.state_version,
next_state=next_state,
event_refs=result.emitted_event_refs,
)
await self.event_bus.emit(step_finished(state, context, result))
if result.outcome != "completed":
break

这段骨架故意不把框架 API 写死。核心不变量是:

先读稳定 State
-> 在隔离副本上执行 Node
-> 生成 State Patch
-> 校验迁移
-> CAS 持久化
-> 发出可关联事件

真实实现还需处理取消、心跳、租约过期、Outbox、事件去重、超时和恢复。


15. 大厂与主流框架方案对照#

公开方案Harness 强项本文借鉴需要自行补齐
OpenAI Agents SDKRunner loop、handoff、guardrail、session、traceRun 与执行 Hook业务事务与资源授权
Anthropic patterns简单组合、workflow/agent 分治、工具接口动态性按收益引入持久 Runtime 需自行实现/选型
Google ADKAgent/Runner/Event/Session/State/Artifact事件与状态分离业务真相和审计模型
Vertex AI Agent Engine托管部署、session、memory/eval 集成Runtime 与开发包分层平台锁定与数据治理评估
Microsoft AutoGenActor/message runtime、AgentChat、扩展消息驱动和多 Agent高风险业务执行约束
AWS Bedrock AgentsAction Group、Knowledge Base、Guardrail云能力组合与编排业务状态验证和跨系统幂等
LangGraph显式 StateGraph、checkpoint、interrupt、replayTyped State 与 durable node工具业务语义仍需建模
TemporalDurable Workflow、activity retry、signal长任务与恢复语义LLM/Context/Eval 抽象

公开文档只能说明产品提供的能力与抽象,不能证明厂商内部 Agent 完全采用同一架构。本文对照是工程综合,不是内部实现披露。

更多官方入口见 [S1][S10]


16. 前沿论文给 Harness 的具体启发#

16.1 从“答案”转向“环境结果”#

AgentBench、WebArena、SWE-bench、τ-bench 把模型放入交互环境,通过任务结果、工具交互或环境状态评估 Agent。[P8] [P9] [P10] [P11]

Harness 落点:

每次动作有结构化记录
每个任务有外部成功条件
最终状态由环境/业务系统验证
失败可以定位到具体 Step

16.2 Agent-Computer Interface#

SWE-agent 论文强调 Agent-Computer Interface 对代码 Agent 表现的重要性:环境提供怎样的命令、反馈与观察,会影响模型能否有效行动。[P12]

Harness 落点:工具并非越多越好;工具名称、参数、错误、输出截断与观察结构都是模型接口设计。

16.3 评估设计本身会制造错觉#

AI Agents That Matter 讨论成本控制、联合准确率与成本、基准过拟合等 Agent 评估问题。[P13]

Harness 落点:从第一天记录成功 Run 成本、尝试次数、轨迹和版本,不只记录最终正确率。

16.4 反思不是免费修复#

Reflexion、Self-Refine 等工作说明语言反馈可以帮助迭代改进。[P4] [P14] 但 Harness 必须限制反思轮数,并使用独立事实或 Verifier,避免模型用更多 token 重复确认自己的错误。


17. 测试策略:按模块定位失败#

17.1 单元测试#

Input normalization
State transition table
Budget calculation
Context selection
Capability filtering
Policy decisions
State patch validation
Event projection/redaction

17.2 契约测试#

Provider event -> ModelEvent
Tool adapter raw result -> Observation
Checkpoint old schema -> migration/new schema
Internal event -> SSE public event
Policy bundle -> PolicyDecision

17.3 图结构测试#

def test_write_tool_is_unreachable_before_confirmation(graph) -> None:
reachable = graph.reachable_nodes(
start="identify_intent",
blocked_conditions={"confirmation_granted"},
)
assert "create_refund" not in reachable

测试:

  • 所有非终止节点至少有一条出边;
  • 高风险节点只能从授权/确认节点到达;
  • 所有循环都有预算或进度条件;
  • 错误边最终能到重试、人工或失败;
  • 中断节点之前有 Checkpoint。

17.4 恢复测试#

故障注入点:

模型返回一半进程死亡
工具请求发出后进程死亡
工具成功但事件发布失败
Checkpoint 成功但 Worker 丢 lease
人工批准同时 Run 被取消
旧版本 Run 被新版本 Worker 恢复

验收不是“程序没报错”,而是没有重复副作用、状态可解释、最终能收敛。

17.5 Scenario Dataset#

{
"case_id": "H-RF-017",
"initial_state": {
"order_status": "paid",
"logistics_status": "not_shipped",
"hours_since_payment": 72
},
"user_message": "还没发货,给我退款",
"expected_required_facts": ["order", "logistics", "policy:R001"],
"expected_nodes": [
"identify_intent",
"query_order",
"verify_order_ownership",
"query_logistics",
"retrieve_policy",
"decide_eligibility",
"request_confirmation"
],
"forbidden_nodes": ["create_compensation"],
"expected_terminal_status": "awaiting_confirmation"
}

18. Harness 级指标#

18.1 质量#

Task Success Rate
Verified Outcome Rate
Correct Route Rate
Required Fact Recall
Tool Selection / Argument Accuracy
Policy Violation Rate
Human Handoff Precision / Recall
Response Faithfulness

18.2 效率#

Steps per Successful Run
Model Calls per Successful Run
Tool Calls per Successful Run
Tokens / Cost per Successful Run
No-progress Loop Rate
Context Utilization

18.3 可靠性#

Run Success / Failure / Cancellation Rate
Checkpoint Recovery Success Rate
Duplicate Side-effect Rate
Unknown Outcome Reconciliation Rate
Retry Amplification
p50 / p95 / p99 Run Duration
Queue Wait Time

18.4 可诊断性#

Trace Completeness
Version Attribution Coverage
Failure Reproduction Rate
State Transition Integrity
Audit Evidence Coverage

指标分母要清晰。例如 Cost per Run 会奖励早早失败的系统,更有意义的是 Cost per Successful Verified Outcome。


19. Failure Taxonomy#

编号层级示例首要责任模块
H01Entry身份/租户解析错误Gateway
H02Input正规化丢失订单号Normalizer
H03Route退款误路由到物流 FAQRouter
H04Context缺少最新物流事实Context Builder
H05Plan步骤依赖顺序错误Planner
H06Model输出不可解析/被阻断Model Gateway
H07Registry暴露未授权工具Tool Registry
H08Execute参数、超时、远端错误Tool Executor
H09State并发覆盖/非法迁移State Store
H10Verify把请求受理当最终成功Verifier
H11Policyfail-open 或策略版本错Policy Engine
H12HITL等待期间状态过期HITL Gateway
H13Response对用户过度承诺Response Composer
H14Recovery重放造成重复退款Runtime
H15EvidenceTrace 缺版本无法复现Trace/Eval

Failure Taxonomy 的价值是把“模型不够聪明”拆成可归属、可修复、可回归的问题。


20. 常见反模式#

20.1 Giant Prompt Harness#

症状:所有业务、状态、历史、工具与安全规则都在一个 Prompt。

后果:无法单测模块,变更影响不可预测,权限边界只存在于自然语言。

20.2 Messages-as-State#

症状:恢复时重新播放全部消息,希望模型“自己知道做到哪了”。

后果:重复工具、副作用、上下文膨胀、状态无法查询。

20.3 Model-as-Policy-Engine#

症状:模型判断自己是否有权退款。

后果:Prompt 注入、随机性和上下文污染直接变成授权漏洞。

20.4 Tool Success Means Goal Success#

症状:工具返回 200 OK 就生成“退款成功”。

后果:异步受理、事务回滚或状态冲突被掩盖。

20.5 Retry Everything#

症状:任何异常都从 Run 开头或 Tool Call 重新执行。

后果:成本放大、重复副作用、限流雪崩。

20.6 Trace Contains Everything#

症状:为了可观测性记录全部 Prompt、文档、工具结果。

后果:PII、Secret 与跨租户数据进入低权限观测系统。

20.7 Framework Equals Architecture#

症状:把 LangGraph/SDK 的类名直接当领域设计。

后果:状态、错误与业务不变量被框架默认值决定。


21. 项目目录与实践任务#

app/
agents/
state.py
events.py
graph.py
runtime.py
recovery.py
budgets.py
context/
builder.py
policies.py
llm/
gateway.py
events.py
adapters/
tools/
registry.py
executor.py
observations.py
policy/
engine.py
decisions.py
hitl/
gateway.py
review_package.py
persistence/
state_store.py
checkpoint_store.py
event_outbox.py
observability/
tracing.py
audit.py
eval/
hooks.py
tests/
unit/
contract/
graph/
recovery/
scenarios/
docs/
agent_harness_architecture.md
state_transition_table.md
failure_taxonomy.md

实践顺序:

1. 写 Run/Step/Attempt/Event Schema
2. 写 AgentState 与状态迁移表
3. 用 Fake Node 跑通 Runtime
4. 加 CAS Checkpoint 与恢复测试
5. 接 Model Gateway Fake/Adapter
6. 接只读 Tool Executor
7. 加 Policy 与 HITL 暂停
8. 接写工具并做幂等/对账
9. 映射 OTel Trace 与 Audit
10. 跑 Scenario Dataset 和故障注入

22. 达标检查清单#

架构边界#

  • 能区分 Model、Agent、Workflow、Harness、Runtime 与 Control Plane;
  • 每个模块有输入、输出、错误、超时与测试契约;
  • 业务 Domain 不依赖具体 Agent SDK;
  • 工具、Memory、RAG、Planner 通过 Port 接入;
  • 模型不能绕过 Policy 与 Executor。

状态与恢复#

  • Run/Step/Attempt/Call ID 语义清楚;
  • Typed State 有 Schema Version 与 CAS;
  • 所有暂停点前有 Checkpoint;
  • 写操作失败未知时先对账;
  • 能从进程死亡、Worker 换租和人工等待中恢复。

安全与治理#

  • Capability Registry 按身份与状态最小暴露;
  • Policy 输出 allow/deny/obligation;
  • HITL 包含事实、证据、动作、风险和有效期;
  • 人工批准后重新验证业务状态;
  • 对外事件经过脱敏投影。

测试与证据#

  • 图可达性、循环与高风险路径可静态测试;
  • Provider/Tool/Event 有契约测试;
  • 有断点故障注入和重复副作用测试;
  • Trace 能关联全部关键版本;
  • 指标以 Verified Outcome 为核心分母。

23. 面试与架构评审问题#

  1. Harness 与 Agent Framework 有什么区别?
  2. 为什么 AgentState 不能只是一组 messages?
  3. Run、Step 和 Attempt 如何支持重试与恢复?
  4. Checkpoint 应放在哪些节点边界?
  5. 为什么工具返回成功后仍需要 Verifier?
  6. 如何证明人工批准没有被等待期间的状态变化作废?
  7. 如何防止模型看到尚未授权的写工具?
  8. 如何让两个 Worker 不会并发推进同一个 Run?
  9. 为什么内部 Agent Event 不能直接发给前端?
  10. 如何判断 Multi-Agent 是业务分工还是多余复杂度?
  11. 一次失败如何归因到 Context、Planner、Tool 还是 Policy?
  12. 如何从一个 SDK 迁移到另一个而保留业务与评估资产?

24. 参考资料与延伸阅读#

以下资料用于核对厂商公开能力、协议与论文原始结论。不同产品的概念名并不一一等价;本文的统一 Harness 模型属于跨方案工程综合,不代表厂商内部架构。

官方 Runtime、SDK 与工程方案#

[S1] OpenAI Agents SDK#

[S2] Google ADK#

[S3] LangGraph#

[S4] Microsoft AutoGen#

[S5] Anthropic Effective Agents#

[S6] Provider Tool Contracts#

[S7] Guardrails and Risk#

[S8] Temporal#

[S9] OpenTelemetry#

[S10] Cloud Agent Runtimes#

论文与基准#

[P1] Lost in the Middle#

[P2] ReAct#

[P3] Plan-and-Solve#

[P4] Reflexion#

[P5] Toolformer#

[P6] Gorilla#

[P7] ToolLLM#

[P8] AgentBench#

[P9] WebArena#

[P10] SWE-bench#

[P11] τ-bench#

[P12] SWE-agent#

[P13] AI Agents That Matter#

[P14] Self-Refine#


25. 阶段总结#

Agent Harness 的核心不是“把更多模块接到模型旁边”,而是把概率决策嵌入一套确定性的控制协议:

目标有 Run;
执行有 Step 与 Attempt;
过程有 Typed State 与 Event;
动作有权限、幂等和验证;
暂停有 Checkpoint;
恢复有版本与对账;
失败有分类与证据;
上线有评估、预算和审计。

当这些边界成立时,模型可以升级,工具可以增加,规划策略可以替换,Memory 与 RAG 可以演进,而业务系统仍然可测试、可恢复、可治理。反之,一个再长的 Prompt、再强的模型或再流行的框架,也无法替代 Harness 的系统职责。

Agent Harness 架构设计:从大 Prompt 到可测试的模块化运行时
https://jupiter-ws.cn/posts/agent/agent-harness-architecture/
作者
Jupiter
发布于
2026-04-07
许可协议
CC BY-NC-SA 4.0