1110 字
6 分钟
Agent 工程(一):模型之外,都是 Harness 的职责

设计 Agent 服务时,我首先会问:这项能力由模型完成,还是由外部系统完成? 我的判断是,模型只负责基于本次输入生成下一步;记忆、工具、重试、权限和恢复都属于 Harness。

这里的 Harness 不是 Agent 外面的一层包装。它组织上下文、调用模型、执行工具并把结果送回模型,因此决定了 Agent 实际能够做什么。

模型与 Harness 的职责边界

模型每轮只看到本次请求#

对话看起来是连续的,但模型本身不会保存上一次调用。第 10 轮能够引用第 1 轮,是因为调用方再次提交了前九轮历史。

messages = 历史消息 + 当前输入
response = model(messages)

这条事实直接划定了几个边界:

  • 历史消息由 Harness 保存和裁剪;
  • 长期记忆由 Harness 检索并放回上下文;
  • Context 超限时,由 Harness 决定压缩、丢弃或中止;
  • 服务重启后能否继续,取决于外部状态是否持久化。

因此,“模型有记忆”通常只是交互体验。工程上真正存在的是一套上下文组装与状态恢复机制。

Agent Loop 是一段受控循环#

最小 Agent Loop 只有四步:

组装上下文 → 调用模型 → 执行工具 → 回填结果
↘ 无工具调用则结束

模型可以提出 tool_use,但它不会真正执行数据库查询、Shell 命令或 HTTP 请求。Harness 必须验证参数、检查权限、执行工具,再用对应的 tool_result 把结果交还模型。

这意味着两类职责不能混淆:

模型负责Harness 负责
理解目标与生成下一步保存状态与组织上下文
选择是否调用工具校验、授权并执行工具
根据结果调整计划超时、重试、取消和审计
判断语义上是否完成验证外部结果是否真实成立

模型适合处理语义判断;Harness 适合执行确定性约束。把重试、权限或状态恢复写进 Prompt,只是希望模型记得遵守,并没有建立可靠机制。

这条边界如何指导设计#

以后遇到一个新能力,我会先判断它是否需要跨模型调用持续存在。

  • 需要跨轮保存:进入 Message、Checkpoint 或 Memory;
  • 需要访问外部世界:进入 Tool 与执行面;
  • 需要保证每次都遵守:进入权限、校验或状态机;
  • 需要判断内容是否合理:交给模型或独立 Verifier。

以“任务完成”为例,模型可以说“已经完成”,但 Harness 仍应检查文件是否生成、测试是否通过、外部任务是否到达终态。语义判断和事实验证必须分开。

这也是后续文章讨论 Checkpoint工具边界评估的共同起点:

只要某项能力不能从本次模型请求中自然得到,它就必须在 Harness 中有明确位置。

我的工程选择#

我会保持 Agent Loop 尽量小,只让它依赖稳定的 Message、Tool、State 和 Event 合同。具体模型、存储和执行后端通过边界接入,不把基础设施差异扩散到循环内部。

这样做的代价是 Harness 需要承担更多工程工作,但收益也很明确:模型可以替换,状态可以恢复,工具可以审计,失败也能够被定位。只有在无状态、无工具、失败后直接重试即可的单轮任务中,我才会省略这些边界;一旦能力需要跨轮持续或产生外部副作用,就应回到显式 Harness。

实现依据:Tool 配对与停止原因

一次工具调用至少要保留稳定的 tool_use_id。模型发出的 tool_use 与调用方返回的 tool_result 必须配对,否则模型无法知道结果属于哪个动作。

stop_reason 也不是模型单方面决定的完成状态:

类型主要含义
end_turn当前生成自然结束
tool_use模型正在等待工具结果
max_tokens调用方设置的输出上限被触发
stop_sequence命中调用方提供的停止序列

因此 Harness 不能把所有停止都解释为任务完成,而要结合工具状态与外部 Verifier 判断下一步。