核心观点
Agency comes from the model. The harness makes agency real.
模型的智能是训练出来的,但让智能在实际环境中可靠运作,需要工程。这个工程就是 Harness。
本文回答三个根本问题:
为什么纯模型输出无法构成可用的 Agent 系统?
Harness 必须解决哪些核心问题?
设计可靠 Agent 系统的原则是什么?
第一部分:问题的本质——为什么需要 Harness 1.1 一个具体场景 用户请求:
“查一下北京今天的天气,如果温度超过 30 度,发邮件提醒团队注意防暑”
纯模型系统的典型回应:
我需要: 1. 查询北京天气 API 获取当前温度 2. 判断温度是否 > 30°C 3. 如果是,调用邮件服务发送提醒 但我没有访问这些工具的能力,建议您手动操作。
问题诊断:
观察
问题
模型”理解”了任务
生成了正确的执行计划
但系统没有”执行”
没有调用任何实际工具
没有机制将描述转化为动作
缺少执行层
这就是需要 Harness 的根本原因:模型生成的是意图描述,系统需要的是动作执行 。
1.2 从描述到执行的鸿沟 ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 用户请求 │ ──▶ │ 模型理解 │ ──▶ │ 意图描述 │ │ "查天气发邮件" │ │ "需要调用 API │ │ "我将调用... │ │ │ │ 然后发邮件" │ │ 但我做不到" │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ▼ ┌─────────────┐ │ 系统停止 │ │ 无动作执行 │ └─────────────┘
核心矛盾:
模型输出是自然语言 (灵活的、模糊的、需要解释的)
系统执行需要结构化指令 (确定的、可解析的、可直接执行的)
Harness 的存在,就是填补这个转换鸿沟。
1.3 没有 Harness 的三种失败模式 失败模式 A:伪执行
模型生成看起来像代码的文本,但系统不执行:
Action: query_weather(city="北京")
这只是文本,不是函数调用。没有 Harness 解析和执行,它就停留在文本层面。
失败模式 B:无状态执行
即使能调用单个工具,多步骤任务会失败:
Step 1: 调用天气 API → 返回 35°C Step 2: 模型应该判断 > 30°C 并决定发邮件 ↓ 问题:Step 1 的结果如何传递给 Step 2 的决策?
没有状态管理,每步都是孤立的,无法形成连贯的任务流。
失败模式 C:失败即终止
Step 1: 调用天气 API → 超时失败 ↓ 系统:报错,任务失败 ↓ 理想行为:重试、换备用源、或向用户解释并请求确认
没有错误恢复策略,任何失败都是任务终止。
第二部分:Harness 的核心职责 2.1 Harness 的定义 Harness 是连接模型智能与实际执行的工程层:
┌─────────────────────────────────────────────────────────────┐ │ Harness Layer │ ├─────────────────────────────────────────────────────────────┤ │ Input Preparation │ │ - 组装用户输入、历史上下文、工具描述 │ │ - 格式化模型可理解的上下文结构 │ ├─────────────────────────────────────────────────────────────┤ │ Decision Parsing │ │ - 解析模型输出:直接回答 vs 工具调用 │ │ - 提取结构化信息:工具名、参数、终止信号 │ ├─────────────────────────────────────────────────────────────┤ │ Tool Execution │ │ - 调用实际工具(本地函数、API、外部服务) │ │ - 捕获执行结果或错误 │ ├─────────────────────────────────────────────────────────────┤ │ State Management │ │ - 将工具结果注入下一轮上下文 │ │ - 维护任务执行轨迹 │ ├─────────────────────────────────────────────────────────────┤ │ Control Flow │ │ - 判定终止条件(完成/最大轮次/错误阈值) │ │ - 防止无限循环 │ └─────────────────────────────────────────────────────────────┘
2.2 核心问题一:结构化契约 模型与系统之间需要一套通信协议。
最简契约设计:
{ "thought" : "用户需要查北京天气,我应该调用 weather 工具" , "action" : { "tool" : "weather" , "parameters" : { "city" : "北京" } } }
或终止状态:
{ "thought" : "温度 35°C > 30°C,需要发送邮件" , "action" : { "tool" : "send_email" , "parameters" : { "to" : "team@example.com" , "subject" : "高温提醒" , "body" : "北京今日 35°C,请注意防暑" } } }
或最终完成:
{ "thought" : "邮件已发送,任务完成" , "action" : null , "final_answer" : "已完成:查询北京天气(35°C),发送高温提醒邮件" }
契约的关键属性:
属性
为什么重要
结构化
系统可解析,无需 NLP 理解
显式终止
系统知道何时停止,不依赖启发式
可追溯
每个决策有 thought,便于调试
2.3 核心问题二:状态循环 多步任务需要维持状态。
Round 1: Input: [用户请求] LLM: {thought, action: weather(city="北京")} Execute: weather API Result: {temp: 35, condition: "sunny"} Round 2: Input: [用户请求] + [Round 1 LLM输出] + [Round 1 工具结果] LLM: {thought, action: email(to="team", ...)} Execute: email service Result: {sent: true} Round 3: Input: [历史] + [Round 2 工具结果] LLM: {thought, action: null, final_answer} → Terminate
状态管理的关键决策:
决策点
选项
适用场景
上下文长度
全保留 vs 摘要 vs 截断
长会话用摘要或分层
失败记录
保留 vs 丢弃
调试时保留,生产可摘要
工具结果
完整 vs 截断
大输出必须截断
2.4 核心问题三:错误恢复 生产系统必须处理失败。
错误分类与策略:
错误类型
示例
恢复策略
参数错误
缺少必需参数 city
向模型反馈,请求修正
工具不存在
调用了未注册的工具
返回可用工具列表
执行超时
API 响应 > 30s
重试(最多 N 次)或换备用源
依赖失败
邮件服务不可用
降级:记录到队列,稍后重试
权限错误
无权访问文件
请求用户确认或降级为只读
错误反馈的结构化:
{ "success" : false , "error_type" : "PARAMETER_MISSING" , "error_message" : "Required parameter 'city' not provided" , "suggested_action" : "Please provide a valid city name" , "retry_possible" : true }
这种结构化错误允许模型做出明智的恢复决策,而不是简单终止。
第三部分:设计原则 3.1 原则一:确定性优先 问题: 模型输出有随机性,如何让系统行为可预期?
策略:
约束输出空间 :通过工具描述和 prompt 引导,减少模型选择范围
强制结构化 :要求特定 JSON 格式,拒绝模糊输出
代码层兜底 :关键约束(轮次上限、超时)在代码中强制执行,不依赖模型自律
def safe_harness_loop (messages, max_rounds=10 , timeout=300 ): for round_num in range (max_rounds): response = call_llm(messages, timeout=timeout) decision = parse_decision(response) if not decision.is_valid(): return ErrorResult("Invalid decision format" ) if decision.should_stop: return decision.final_answer result = execute_tool(decision.action) messages = update_context(messages, result)
3.2 原则二:分层解耦 问题: 功能增长导致复杂度爆炸。
策略: 将 Harness 拆分为独立层:
┌────────────────────────────────────────┐ │ Agent Entry Layer │ │ - 请求路由、权限检查、日志记录 │ ├────────────────────────────────────────┤ │ Orchestration Layer │ │ - 任务分解、子 Agent 管理、结果聚合 │ ├────────────────────────────────────────┤ │ Execution Layer (Harness) │ │ - 循环控制、工具调用、状态管理 │ ├────────────────────────────────────────┤ │ Tool Layer │ │ - 具体工具实现、错误处理、重试逻辑 │ ├────────────────────────────────────────┤ │ Infrastructure Layer │ │ - 模型客户端、存储、监控、告警 │ └────────────────────────────────────────┘
分层的好处:
每层可独立测试
新工具接入不改动循环逻辑
可替换底层实现(如换模型提供商)不影响上层
3.3 原则三:可观测内建 问题: 系统黑盒运行,故障难以定位。
策略: 从设计之初就包含观测点:
@dataclass class ExecutionTrace : request_id: str rounds: List [RoundTrace] final_result: Optional [str ] termination_reason: str total_latency_ms: int @dataclass class RoundTrace : round_number: int llm_latency_ms: int decision_type: str tool_name: Optional [str ] tool_result_summary: str tool_latency_ms: int error: Optional [str ]
可观测性要回答的问题:
这个请求经历了多少轮?
每轮调用了什么工具?成功了吗?
总耗时多少?瓶颈在哪?
失败时,最后的状态是什么?
能否复现这次执行?
3.4 原则四:渐进增强 问题: 试图一次性构建完美系统,导致无法交付。
策略: 从最小可用开始,逐层增强:
Phase 1: 单工具循环 - 一个 LLM 调用 - 一个工具 - 状态在内存 Phase 2: 多工具支持 - 工具注册表 - 工具描述优化 Phase 3: 状态持久化 - 任务可以跨会话 - 支持长时间运行 Phase 4: 可靠性增强 - 超时、重试、降级 - 错误恢复策略 Phase 5: 多 Agent 协作 - 任务分解 - Agent 间通信 - 物理隔离
每一层都独立可用,不依赖下一层。
第四部分:关键决策对照表 4.1 架构决策
决策点
选项 A
选项 B
选择建议
工具注册
静态注册(启动时)
动态注册(运行时)
生产用静态,调试/扩展用动态
状态存储
内存
文件/数据库
短时间任务用内存,长时间用持久化
上下文维护
全保留
摘要压缩
对话用压缩,关键任务用全保留
错误处理
终止
反馈模型
可恢复错误反馈模型,致命错误终止
多 Agent
共享进程
独立进程
高隔离需求用独立,简单场景用共享
4.2 实现检查清单 最小可用 Harness:
生产级 Harness:
企业级 Harness:
结语:Harness 工程的本质 Harness 不是让模型变聪明,而是让模型的智能能够可靠地、可观测地、可恢复地 作用于现实世界。
理解这一点,就不会陷入”Prompt 调优能解决一切”的误区,也不会过度设计试图用代码实现智能。
好的 Harness 工程师知道:
智能来自模型,自己的任务是构建环境
可靠性来自结构设计,不来自更强的模型
可观测性和错误恢复与功能同等重要
从简单开始,让真实需求驱动复杂度增长
下一篇文章,我们将通过 learn-claude-code 项目的 12 层实践,展示这些原则如何落地为可运行的代码。