Agent Harness 01:核心架构与术语
先把四个容易混淆的对象分开
| 对象 | 负责什么 | 不负责什么 |
|---|---|---|
| 模型(model) | 根据上下文产生文本、动作或结构化输出 | 权限、事务、超时、真实成功判定 |
| Agent 策略(policy) | 决定下一动作,可能采用 ReAct、规划或搜索 | 动作能否安全执行 |
| Harness / runtime | 承接策略,治理上下文、工具、环境、状态与生命周期 | 业务本身的目标定义 |
| Benchmark / evaluator | 初始化任务并从环境状态判分 | 生产执行时的权限治理 |
一个产品可以用 LangGraph 表达控制流,用 OpenHands 提供沙箱,用某个模型做策略,再用 SWE-bench 风格的测试做验收。这些名字不能替代边界设计。
数据面与控制面
Harness 最有用的拆法,不是“有几个 Agent”,而是区分数据面和控制面。
数据面:Task -> Context -> Model -> Action -> Tool -> Observation -> Context
控制面:身份 / 权限 / 预算 / 超时 / 审批 / checkpoint / trace / evaluator数据面内容可以由模型参与生成;控制面必须由普通程序掌握。模型可以建议“再试一次”,但不能自行提高 max_steps;可以请求写文件,但不能自行把只读工具升级成写工具;可以总结失败,却不能独自宣告测试通过。
一次 step 的严格语义
建议把每轮定义成下面的状态转移:
- :可恢复的 Agent 状态;
- :剩余 step、token、金额、时间等预算;
- :模型原始输出;
- :解码并通过 schema 校验的候选动作;
- :策略门允许或改写后的动作;
- :给模型看的有界观察;
- :真实环境副作用,必须进入审计日志。
这一定义迫使实现回答三个问题:原始输出是否保留?被拒动作是否记入状态?观察摘要能否追溯到未截断原文?若回答不了,就还不具备重放和事故审计能力。
九层参考架构
1. TaskSpec:任务契约
至少包含:
goal: "修复 issue 并让指定测试通过"
success:
evaluator: "pytest tests/regression_test.py"
constraints:
- "不得改动测试"
- "不得访问公网"
budgets:
steps: 30
wall_time_s: 900
model_tokens: 120000
artifacts:
- "git diff"
- "test report"目标文字用于指导模型;evaluator 与约束由 harness 执行。两者不能合并为一句自然语言。
2. ModelAdapter:隔离模型差异
适配器统一:消息角色、tool schema、流式响应、停止原因、重试、用量和 provider request id。上层只接收 Action | Final | ModelError,不要到处散落厂商字段判断。
重试只覆盖可判定的瞬时失败,例如连接中断和限流;模型已经产生但客户端未收到确认时,要用请求 id 或 trace 判断是否可安全重放。
3. ContextBuilder:上下文是一种资源调度
上下文不是聊天记录全量拼接。一个可控构造器按优先级组织:
- 系统规则和能力边界;
- 当前目标与验收条件;
- 当前环境状态和最近动作;
- 与当前决策相关的历史;
- 工具说明与示例;
- 可丢弃的详细日志。
每一块记录来源、版本、token 数与是否可信。摘要是派生缓存,不应覆盖原始事件。
4. ActionProtocol:把意图变成类型
推荐使用 tagged union,而不是从自然语言里找命令:
{"kind":"tool","name":"read_file","arguments":{"path":"src/a.py"}}
{"kind":"final","answer":"已修复并通过 18 项测试"}Schema 校验只说明“格式正确”,不说明“动作允许”。路径范围、金额、收件人、网络域名和数据敏感级别由后续策略门判断。
5. PolicyGate:确定性的能力系统
策略至少组合四类判断:
| 类型 | 示例 |
|---|---|
| 身份 | 当前用户能否调用部署工具 |
| 能力 | 此任务是否授予 repo.write |
| 参数 | 路径是否位于 workspace,金额是否低于阈值 |
| 情境 | 是否来自未信任观察,是否已获人类审批 |
“dangerous: true” 只是标签,不是防护。真正的防护是执行器没有未授权能力,且策略在执行前检查结构化参数。
6. Executor:副作用只能从一个窄口发生
执行器负责超时、资源限额、工作目录、环境变量、网络、文件系统边界和进程清理。研究原型至少使用临时工作区;生产系统通常需要容器、微虚拟机或远程受限执行环境。
不要把 subprocess(shell=True) 暴露为通用工具,再指望 prompt 约束它。若必须提供 shell,也应限制身份、工作区、网络和凭据,并记录完整命令与退出状态。
7. ObservationAdapter:为模型设计反馈
好的观察满足四点:
- 有界:超长内容分页或截断,同时保存原文引用;
- 有结构:退出码、stdout、stderr、变更摘要分开;
- 有来源:标明 user、tool、web、repo 等信任域;
- 可行动:错误告诉模型下一步能修什么,不泄漏不必要内部信息。
SWE-agent 的 ACI 研究说明,反馈格式并非装饰,而会影响模型的导航和修错行为。
8. StateStore:事件先于“记忆”
先记录不可变事件流,再构造工作记忆、摘要与检索索引:
events.jsonl # 原始事实,可审计
checkpoint.json # 可恢复状态
context_snapshot.json # 某轮实际送入模型的内容
artifacts/ # diff、截图、测试报告“长期记忆”不是默认正确。错误反思被永久召回,会系统性放大偏差;先让记忆有来源、有效期和删除机制。
9. Evaluator:与 Agent 解耦的裁判
优先级从强到弱:
- 数据库/文件/应用的目标状态;
- 可执行测试和不变量;
- 精确或规范化后的输出;
- 独立模型裁判;
- 被测 Agent 自评。
后两项只适合无法程序化的软目标,并应抽样做人工校准。评测器的凭据和隐藏测试不能进入 Agent 上下文。
终止不是一个 break
Harness 应区分至少六种终态:
| 终态 | 含义 | 是否可自动重试 |
|---|---|---|
SUCCEEDED | 独立 evaluator 通过 | 无需 |
FAILED | evaluator 明确否定且策略无后续动作 | 视任务而定 |
BUDGET_EXHAUSTED | step/token/时间/费用用尽 | 提高预算前先分析轨迹 |
POLICY_BLOCKED | 动作被权限或审批规则阻止 | 需要授权或改计划 |
ENVIRONMENT_ERROR | 沙箱、依赖或外部服务异常 | 通常可重试 |
MODEL_ERROR | 模型调用或协议持续失败 | 可能换模型/适配器 |
混成一个 error 会让成功率、成本和稳定性分析失真。
设计评审清单
- 成功由谁判定?是否检查环境状态?
- 模型能否修改自己的预算、工具表或系统规则?
- 每个副作用是否经过同一个策略门?
- 工具结果属于哪种信任域?
- 中断后从哪里恢复,重试是否可能重复副作用?
- 是否保存实际模型输入、原始输出与版本?
- 观察被截断后,原文是否仍可审计?
- 用新模型替换旧模型时,评测是否固定?
- 安全评测是否包含攻击成功率,而不只有任务成功率?