核心观点

Agency comes from the model. The harness makes agency real.

模型的智能是训练出来的,但让智能在实际环境中可靠运作,需要工程。这个工程就是 Harness。

本文回答三个根本问题:

  1. 为什么纯模型输出无法构成可用的 Agent 系统?
  2. Harness 必须解决哪些核心问题?
  3. 设计可靠 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 原则一:确定性优先

问题: 模型输出有随机性,如何让系统行为可预期?

策略:

  1. 约束输出空间:通过工具描述和 prompt 引导,减少模型选择范围
  2. 强制结构化:要求特定 JSON 格式,拒绝模糊输出
  3. 代码层兜底:关键约束(轮次上限、超时)在代码中强制执行,不依赖模型自律
# 强制约束示例
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_use" | "stop" | "error"
tool_name: Optional[str]
tool_result_summary: str
tool_latency_ms: int
error: Optional[str]

可观测性要回答的问题:

  1. 这个请求经历了多少轮?
  2. 每轮调用了什么工具?成功了吗?
  3. 总耗时多少?瓶颈在哪?
  4. 失败时,最后的状态是什么?
  5. 能否复现这次执行?

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:

  • 多 Agent 协作与通信
  • 物理隔离(worktree/container)
  • 权限治理和审计
  • 监控、告警、可观测平台
  • 配置管理和版本控制

结语:Harness 工程的本质

Harness 不是让模型变聪明,而是让模型的智能能够可靠地、可观测地、可恢复地作用于现实世界。

理解这一点,就不会陷入”Prompt 调优能解决一切”的误区,也不会过度设计试图用代码实现智能。

好的 Harness 工程师知道:

  • 智能来自模型,自己的任务是构建环境
  • 可靠性来自结构设计,不来自更强的模型
  • 可观测性和错误恢复与功能同等重要
  • 从简单开始,让真实需求驱动复杂度增长

下一篇文章,我们将通过 learn-claude-code 项目的 12 层实践,展示这些原则如何落地为可运行的代码。