设计 Agent 服务时,我首先会问:这项能力由模型完成,还是由外部系统完成? 我的判断是,模型只负责基于本次输入生成下一步;记忆、工具、重试、权限和恢复都属于 Harness。
这里的 Harness 不是 Agent 外面的一层包装。它组织上下文、调用模型、执行工具并把结果送回模型,因此决定了 Agent 实际能够做什么。
模型每轮只看到本次请求
对话看起来是连续的,但模型本身不会保存上一次调用。第 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 判断下一步。