Agent Harness 架构设计:从大 Prompt 到可测试的模块化运行时
本文是 精英 Agent 工程师学习路线:从范式理解到生产级落地 阶段 3 的配套学习笔记。 核心结论:模型只是 Harness 中一个可替换的决策组件。真正决定系统能否生产运行的,是状态、事件、工具执行、恢复、安全、人工接管、评估和观测如何围绕模型形成闭环。
0. 学习目标、范围与非目标
学完本文后,你应该能够:
- 解释 Model、Agent、Workflow、Harness、Runtime 与 Control Plane 的边界;
- 把任意 Agent 业务拆成输入、状态、上下文、决策、动作、验证和证据模块;
- 定义一次 Run、一次 Step、一次 Attempt 与一次 Tool Call 的生命周期;
- 明确每个模块的输入、输出、失败语义、超时、幂等和测试方式;
- 设计能够 Checkpoint、暂停、恢复、取消和人工接管的状态机;
- 避免让对话消息同时承担业务状态、执行日志与长期记忆;
- 用统一事件信封连接 API、编排器、工具、Trace、Eval 与前端流式展示;
- 为
OrderFlow-Agent画出可落地的 Harness 架构并写出核心接口。
本文只定义 Harness 的系统边界,不提前重复后续专篇:
| 后续阶段 | 本文只讲 | 后续专篇深入 |
|---|---|---|
| 阶段 4 工具/MCP/Skills | Tool Registry 与 Executor 接口 | 协议、Schema、授权、供应链 |
| 阶段 5 Memory | Context Port 与 Memory Port | 写入策略、冲突、删除、评估 |
| 阶段 6 RAG | Evidence Retriever 接口 | Hybrid、GraphRAG、RuleRAG |
| 阶段 7 Planning | Planner/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 OutcomeOpenAI 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_eligibility | Typed AgentState |
| 业务状态 | 订单已退款 | 订单/退款服务真相源 |
| 执行状态 | 工具 Attempt 2 超时 | Run/Step/Attempt records |
| 学习状态 | 此失败已进入回归集 | Eval dataset / failure store |
最危险的设计是把五类状态都藏在消息列表里,因为消息无法提供事务约束、并发控制、结构化查询和可靠恢复。
3. 一次 Agent Run 的完整生命周期
01 Receive Request02 Authenticate & Authorize Entry03 Normalize Input04 Create Run + Pin Versions05 Load Checkpoint / Initialize State06 Build Minimal Context07 Route or Plan Next Step08 Call Model or Deterministic Node09 Validate Proposed Action10 Execute Tool with Policy11 Persist Observation + State Transition12 Verify Goal / Business State13 Continue, Pause, Handoff, or Terminate14 Emit Response + Trace + Eval Hooks3.1 Run、Step、Attempt、Call 不可混用
Run└── Step: query_order ├── Attempt 1 │ └── Tool Call: timeout └── Attempt 2 └── Tool Call: success| 层级 | ID | 作用 |
|---|---|---|
| Request | request_id | HTTP/API 去重与客户端关联 |
| Run | run_id | 一次目标执行生命周期 |
| Thread | thread_id | 多次 Run 的会话/任务范围 |
| Step | step_id | 逻辑节点执行 |
| Attempt | attempt_id | Step 的某次尝试 |
| Model Call | model_call_id | 一次模型请求 |
| Tool Call | tool_call_id | 一次工具执行请求 |
| Trace | trace_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 Gateway | HTTP/event | authenticated request | 重放、伪造、超载 | auth、rate limit |
| Input Normalizer | raw input | canonical request | 丢语义、编码攻击 | 边界/模糊测试 |
| Intent/Slot | request + history | typed intent | 错误路由、漏槽位 | accuracy、slot F1 |
| Context Builder | state + sources | context package | 污染、超预算、泄露 | precision、ACL |
| Capability Router | intent + policy | candidate capabilities | 能力过曝、漏召回 | routing recall |
| Planner | goal + state | plan/next action | 循环、过时计划 | validity、efficiency |
| Model Gateway | model request | normalized events | 限流、格式漂移 | adapter contract |
| Tool Registry | identity + state | allowed tool specs | 描述投毒、版本漂移 | registry snapshot |
| Tool Executor | validated call | observation | 越权、重复副作用 | policy/idempotency |
| State Store | transition | checkpoint | 并发覆盖、无法恢复 | CAS/replay |
| Verifier | state + evidence | pass/fail/repair | 自我确认偏差 | verifier recall |
| Guardrails/Policy | subject/action/resource | allow/deny/obligations | fail-open | adversarial tests |
| HITL | review package | human decision | 信息不足、重复执行 | handoff precision |
| Response Composer | verified state | user response | 过度承诺、泄露 | faithfulness |
| Trace/Eval | events + outcome | evidence/metrics | PII、不可复现 | schema/completeness |
关键设计要求:每个模块都必须有明确失败输出,而不是只定义成功返回值。
5. AgentState:恢复协议,不是随手字典
5.1 Typed State
from datetime import datetimefrom typing import Literalfrom 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: datetime5.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_runsSET state = :new_state, state_version = state_version + 1WHERE 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 PrecisionRequired Fact RecallStale Context RateUnauthorized Context RateInstruction/Data Separation Pass RateTokens per Successful Step7. 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: str8.2 归一化事件
model.startedmodel.text.deltamodel.tool_call.deltamodel.output.completedmodel.usage.completedmodel.blockedmodel.failed8.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 + schemaHarness: ActionRequest ↓ authz + policy + confirmationExecutor: ToolCommand ↓ adapterRemote System ↓ normalize + verifyHarness: Observation9.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: datetimeoutcome_unknown 是关键状态:写请求可能已经被远端受理,只是响应丢失。Harness 应先调用查询/对账工具,不可直接重复写。
9.3 工具循环终止
ReAct 证明交替生成 reasoning/action 与读取 observation 能增强交互任务能力;Toolformer、Gorilla、ToolLLM 则探索模型学习工具使用和大规模 API 调用。[P2] [P5] [P6] [P7] Harness 仍必须提供:
max_stepsmax_tool_callsper_tool_limitduplicate_call_detectorno_progress_detectorwall_clock_deadlinecost_budgetterminal_state_checker10. 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 | Noneobligations 可能是:
mask.phonelimit.refund_amount=5000log.audit.high_riskverify.order_ownerconfirm.user.v210.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 VersionsCurrent Typed StateCompleted Step IDsPending External OperationRetry ScheduleHuman/Confirmation TokenBudget UsageLast Durable Event Sequence11.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 outcome11.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.failedstep.started / step.completed / step.failedmodel.started / model.completed / model.failedtool.proposed / tool.authorized / tool.completed / tool.failedstate.transitioned / checkpoint.savedhuman.requested / human.decidedbudget.warning / budget.exhaustedeval.queued / eval.completed12.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 Collector13.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_response13.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 SDK | Runner loop、handoff、guardrail、session、trace | Run 与执行 Hook | 业务事务与资源授权 |
| Anthropic patterns | 简单组合、workflow/agent 分治、工具接口 | 动态性按收益引入 | 持久 Runtime 需自行实现/选型 |
| Google ADK | Agent/Runner/Event/Session/State/Artifact | 事件与状态分离 | 业务真相和审计模型 |
| Vertex AI Agent Engine | 托管部署、session、memory/eval 集成 | Runtime 与开发包分层 | 平台锁定与数据治理评估 |
| Microsoft AutoGen | Actor/message runtime、AgentChat、扩展 | 消息驱动和多 Agent | 高风险业务执行约束 |
| AWS Bedrock Agents | Action Group、Knowledge Base、Guardrail | 云能力组合与编排 | 业务状态验证和跨系统幂等 |
| LangGraph | 显式 StateGraph、checkpoint、interrupt、replay | Typed State 与 durable node | 工具业务语义仍需建模 |
| Temporal | Durable Workflow、activity retry、signal | 长任务与恢复语义 | LLM/Context/Eval 抽象 |
公开文档只能说明产品提供的能力与抽象,不能证明厂商内部 Agent 完全采用同一架构。本文对照是工程综合,不是内部实现披露。
16. 前沿论文给 Harness 的具体启发
16.1 从“答案”转向“环境结果”
AgentBench、WebArena、SWE-bench、τ-bench 把模型放入交互环境,通过任务结果、工具交互或环境状态评估 Agent。[P8] [P9] [P10] [P11]
Harness 落点:
每次动作有结构化记录每个任务有外部成功条件最终状态由环境/业务系统验证失败可以定位到具体 Step16.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 normalizationState transition tableBudget calculationContext selectionCapability filteringPolicy decisionsState patch validationEvent projection/redaction17.2 契约测试
Provider event -> ModelEventTool adapter raw result -> ObservationCheckpoint old schema -> migration/new schemaInternal event -> SSE public eventPolicy bundle -> PolicyDecision17.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 RateVerified Outcome RateCorrect Route RateRequired Fact RecallTool Selection / Argument AccuracyPolicy Violation RateHuman Handoff Precision / RecallResponse Faithfulness18.2 效率
Steps per Successful RunModel Calls per Successful RunTool Calls per Successful RunTokens / Cost per Successful RunNo-progress Loop RateContext Utilization18.3 可靠性
Run Success / Failure / Cancellation RateCheckpoint Recovery Success RateDuplicate Side-effect RateUnknown Outcome Reconciliation RateRetry Amplificationp50 / p95 / p99 Run DurationQueue Wait Time18.4 可诊断性
Trace CompletenessVersion Attribution CoverageFailure Reproduction RateState Transition IntegrityAudit Evidence Coverage指标分母要清晰。例如 Cost per Run 会奖励早早失败的系统,更有意义的是 Cost per Successful Verified Outcome。
19. Failure Taxonomy
| 编号 | 层级 | 示例 | 首要责任模块 |
|---|---|---|---|
| H01 | Entry | 身份/租户解析错误 | Gateway |
| H02 | Input | 正规化丢失订单号 | Normalizer |
| H03 | Route | 退款误路由到物流 FAQ | Router |
| H04 | Context | 缺少最新物流事实 | Context Builder |
| H05 | Plan | 步骤依赖顺序错误 | Planner |
| H06 | Model | 输出不可解析/被阻断 | Model Gateway |
| H07 | Registry | 暴露未授权工具 | Tool Registry |
| H08 | Execute | 参数、超时、远端错误 | Tool Executor |
| H09 | State | 并发覆盖/非法迁移 | State Store |
| H10 | Verify | 把请求受理当最终成功 | Verifier |
| H11 | Policy | fail-open 或策略版本错 | Policy Engine |
| H12 | HITL | 等待期间状态过期 | HITL Gateway |
| H13 | Response | 对用户过度承诺 | Response Composer |
| H14 | Recovery | 重放造成重复退款 | Runtime |
| H15 | Evidence | Trace 缺版本无法复现 | 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.pytests/ unit/ contract/ graph/ recovery/ scenarios/docs/ agent_harness_architecture.md state_transition_table.md failure_taxonomy.md实践顺序:
1. 写 Run/Step/Attempt/Event Schema2. 写 AgentState 与状态迁移表3. 用 Fake Node 跑通 Runtime4. 加 CAS Checkpoint 与恢复测试5. 接 Model Gateway Fake/Adapter6. 接只读 Tool Executor7. 加 Policy 与 HITL 暂停8. 接写工具并做幂等/对账9. 映射 OTel Trace 与 Audit10. 跑 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. 面试与架构评审问题
- Harness 与 Agent Framework 有什么区别?
- 为什么 AgentState 不能只是一组 messages?
- Run、Step 和 Attempt 如何支持重试与恢复?
- Checkpoint 应放在哪些节点边界?
- 为什么工具返回成功后仍需要 Verifier?
- 如何证明人工批准没有被等待期间的状态变化作废?
- 如何防止模型看到尚未授权的写工具?
- 如何让两个 Worker 不会并发推进同一个 Run?
- 为什么内部 Agent Event 不能直接发给前端?
- 如何判断 Multi-Agent 是业务分工还是多余复杂度?
- 一次失败如何归因到 Context、Planner、Tool 还是 Policy?
- 如何从一个 SDK 迁移到另一个而保留业务与评估资产?
24. 参考资料与延伸阅读
以下资料用于核对厂商公开能力、协议与论文原始结论。不同产品的概念名并不一一等价;本文的统一 Harness 模型属于跨方案工程综合,不代表厂商内部架构。
官方 Runtime、SDK 与工程方案
[S1] OpenAI Agents SDK
[S2] Google ADK
[S3] LangGraph
[S4] Microsoft AutoGen
[S5] Anthropic Effective Agents
- Building effective agents:workflow/agent 边界、组合模式与工具设计。
[S6] Provider Tool Contracts
- OpenAI Function Calling;
- Anthropic Tool use;
- Google Gemini Function calling。
[S7] Guardrails and Risk
- OpenAI Agents SDK Guardrails;
- OWASP Top 10 for LLM Applications;
- OWASP AI Agent Security Cheat Sheet;
- NIST Generative AI Profile。
[S8] Temporal
[S9] OpenTelemetry
- OpenTelemetry Semantic Conventions for Generative AI Systems:独立规范仓库,Agent、inference、tool 与 metrics 仍处 Development 状态。
[S10] Cloud Agent Runtimes
- Google Cloud Vertex AI Agent Engine overview;
- Microsoft Azure AI Foundry Agent Service;
- AWS Agents for Amazon Bedrock。
论文与基准
[P1] Lost in the Middle
- Liu et al., Lost in the Middle: How Language Models Use Long Contexts, TACL 2024。
[P2] ReAct
- Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023。
[P3] Plan-and-Solve
[P4] Reflexion
- Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning, NeurIPS 2023。
[P5] Toolformer
- Schick et al., Toolformer: Language Models Can Teach Themselves to Use Tools, NeurIPS 2023。
[P6] Gorilla
- Patil et al., Gorilla: Large Language Model Connected with Massive APIs, 2023。
[P7] ToolLLM
- Qin et al., ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs, ICLR 2024。
[P8] AgentBench
- Liu et al., AgentBench: Evaluating LLMs as Agents, ICLR 2024。
[P9] WebArena
- Zhou et al., WebArena: A Realistic Web Environment for Building Autonomous Agents, ICLR 2024。
[P10] SWE-bench
- Jimenez et al., SWE-bench: Can Language Models Resolve Real-World GitHub Issues?, ICLR 2024。
[P11] τ-bench
- Yao et al., τ-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domains, 2024。
[P12] SWE-agent
- Yang et al., SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering, NeurIPS 2024。
[P13] AI Agents That Matter
- Kapoor et al., AI Agents That Matter, 2024。
[P14] Self-Refine
- Madaan et al., Self-Refine: Iterative Refinement with Self-Feedback, NeurIPS 2023。
25. 阶段总结
Agent Harness 的核心不是“把更多模块接到模型旁边”,而是把概率决策嵌入一套确定性的控制协议:
目标有 Run;执行有 Step 与 Attempt;过程有 Typed State 与 Event;动作有权限、幂等和验证;暂停有 Checkpoint;恢复有版本与对账;失败有分类与证据;上线有评估、预算和审计。当这些边界成立时,模型可以升级,工具可以增加,规划策略可以替换,Memory 与 RAG 可以演进,而业务系统仍然可测试、可恢复、可治理。反之,一个再长的 Prompt、再强的模型或再流行的框架,也无法替代 Harness 的系统职责。