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 的严格语义

建议把每轮定义成下面的状态转移:

(st,bt)→contextmt→decodeat→policya^t→execute(ot,Δet)→reduce(st+1,bt+1)(s_t, b_t) \xrightarrow{\text{context}} m_t \xrightarrow{\text{decode}} a_t \xrightarrow{\text{policy}} \hat{a}_t \xrightarrow{\text{execute}} (o_t, \Delta e_t) \xrightarrow{\text{reduce}} (s_{t+1}, b_{t+1})

  • sts_t:可恢复的 Agent 状态;
  • btb_t:剩余 step、token、金额、时间等预算;
  • mtm_t:模型原始输出;
  • ata_t:解码并通过 schema 校验的候选动作;
  • a^t\hat{a}_t:策略门允许或改写后的动作;
  • oto_t:给模型看的有界观察;
  • Δet\Delta e_t:真实环境副作用,必须进入审计日志。

这一定义迫使实现回答三个问题:原始输出是否保留?被拒动作是否记入状态?观察摘要能否追溯到未截断原文?若回答不了,就还不具备重放和事故审计能力。

九层参考架构

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:上下文是一种资源调度

上下文不是聊天记录全量拼接。一个可控构造器按优先级组织:

  1. 系统规则和能力边界;
  2. 当前目标与验收条件;
  3. 当前环境状态和最近动作;
  4. 与当前决策相关的历史;
  5. 工具说明与示例;
  6. 可丢弃的详细日志。

每一块记录来源、版本、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 解耦的裁判

优先级从强到弱:

  1. 数据库/文件/应用的目标状态;
  2. 可执行测试和不变量;
  3. 精确或规范化后的输出;
  4. 独立模型裁判;
  5. 被测 Agent 自评。

后两项只适合无法程序化的软目标,并应抽样做人工校准。评测器的凭据和隐藏测试不能进入 Agent 上下文。

终止不是一个 break

Harness 应区分至少六种终态:

终态含义是否可自动重试
SUCCEEDED独立 evaluator 通过无需
FAILEDevaluator 明确否定且策略无后续动作视任务而定
BUDGET_EXHAUSTEDstep/token/时间/费用用尽提高预算前先分析轨迹
POLICY_BLOCKED动作被权限或审批规则阻止需要授权或改计划
ENVIRONMENT_ERROR沙箱、依赖或外部服务异常通常可重试
MODEL_ERROR模型调用或协议持续失败可能换模型/适配器

混成一个 error 会让成功率、成本和稳定性分析失真。

设计评审清单

  • 成功由谁判定?是否检查环境状态?
  • 模型能否修改自己的预算、工具表或系统规则?
  • 每个副作用是否经过同一个策略门?
  • 工具结果属于哪种信任域?
  • 中断后从哪里恢复,重试是否可能重复副作用?
  • 是否保存实际模型输入、原始输出与版本?
  • 观察被截断后,原文是否仍可审计?
  • 用新模型替换旧模型时,评测是否固定?
  • 安全评测是否包含攻击成功率,而不只有任务成功率?

下一步

先读论文谱系理解每一层的实证来源,再用最小实现把数据面和控制面跑通。