Agent Harness Runtime 架构深度解析:工具循环、状态外置与长程任务调度

0 阅读14分钟

拆解 Agent Harness 如何用工具循环、外置状态和 Hook,让模型真正变成可靠交付系统。 原文链接AI 小老六

导语

评估一个 ​Coding Agent​,很多人会先盯模型:是不是换了更大的权重、推理链是不是更长、上下文窗口是不是更宽。但真实工程里经常出现另一种情况:模型没变,Agent 的表现却像换了一代产品。

原因不在模型参数里,而在模型外面那层运行系统里。它决定模型能看到什么上下文、能调用什么工具、在什么环境里执行命令、失败后如何被纠正、长任务跑偏时谁来把它拉回来。Addy Osmani 把这层系统称为 ​Agent Harness Engineering​。用一句工程化的公式概括就是:

agent = model + harness

这个等式的含义并不抽象。Terminal Bench 2.0 上已经出现过很有说服力的案例:同样的模型、同样的任务和预算,只调整 harness,Coding Agent 的排名可以从 Top 30 拉到 Top 5。换句话说,​**Agent 的差距并不总是来自“模型聪不聪明”**​,更多时候来自“模型被怎样接入真实工作流”。

本文不把 Harness 当成一个流行词来解释,而是把它拆成一套可落地的运行时架构:​工具循环、状态持久化、执行沙箱、记忆检索、确定性 Hook、长程任务调度​,以及正在形成的 Harness-as-a-Service 趋势。

inline-01-harness-shell.png

图:Harness 像模型外壳,把推理能力接入工具、状态、约束和真实工作流。

Harness Runtime:模型之外的执行外壳

一个 Agent 不是“会说话的模型”,而是“模型被放进一个可以行动的系统”。这个系统最小也要包含三个东西:​目标、工具、循环​。

Simon Willison 对 Agent 有一个极简定义:Agent 是一个为了达成目标而在循环中使用工具的系统。这个定义听起来朴素,却正好把 Harness 的职责说清楚了:模型负责推理下一步,Harness 负责把“下一步”变成可执行、可观测、可纠偏的动作。

可以把 Harness Runtime 看成下面这几层:

mermaid-01.png

图:Harness Runtime 把目标、工具、环境、状态和约束串成可执行闭环。

这张图里,模型只占 Loop 中的推理部分。真正决定 Agent 行为边界的,是外面这些工程组件:

Harness 层级负责的问题常见实现
指令层让模型理解角色、边界、项目规则System Prompt、AGENTS.mdCLAUDE.md、Skill 描述
工具层把推理结果接入真实世界Bash、文件编辑、浏览器、MCP Server、搜索
环境层让工具调用有安全边界Sandbox、权限审批、网络隔离、默认 CLI
状态层让任务跨轮次、跨会话延续Plan 文件、日志、Git 分支、Memory Store
约束层把“应该做”变成“必须做”PreToolUse、PostToolUse、Stop Hook、Reviewer Agent
观测层让失败可复盘、可迭代Trace、Token/Cost 统计、Session Replay

这也是为什么 “换模型”“改 Harness” 会产生完全不同的工程收益。换模型改变能力上限,改 Harness 改变模型能把上限发挥到什么程度。

失败归因:别急着把锅甩给模型

Agent 一旦翻车,最容易出现的判断是:“模型还是不够强,等下一代吧。”这个判断有时成立,但在工程现场经常过早出现。

HumanLayer 在讨论 Coding Agent 时提出过一个很刺耳的反转:这不是模型问题,而是配置问题。它的意思不是替模型开脱,而是提醒工程师先检查自己能控制的那部分:

  • Tool description 是否让模型知道什么时候该用、什么时候不该用?
  • System Prompt 与项目规则是否互相冲突?
  • 文件系统里的计划、日志和中间产物是否足够清晰?
  • Hook 是否在背后拒绝了工具调用,但错误没有被模型看见?
  • Reviewer 或 Evaluator 是否真的独立于生成者,而不是让 Agent 自己宣布成功?

这类问题都发生在 ​Harness 层​。它们不会因为模型多几个百分点的 benchmark 分数自动消失。

更关键的是,归因方式会改变团队文化。如果团队默认“模型不行”,工程动作就只剩等待。如果团队默认“先查 Harness”,今晚就能动手:重写一个工具描述、把一条项目纪律挪进 Hook、把超长工具输出落盘、给长任务补一个 plan 文件、把自评改成外部 Judge。

Harness Engineering 的价值就在这里:它把不可控的“等模型升级”,拆成一组今天就能改的工程对象。

Ratchet 机制:让每次失败沉淀成约束

好的 Harness 不靠灵感堆配置,而靠失败推动演进。Addy Osmani 对 AGENTS.md 有一个很硬的标准:好的规则文件里,每一行都应该能追溯到一次具体失败。

这句话可以转成一个更可操作的 Ratchet 机制。Ratchet 是棘轮,只允许系统向一个方向积累改进。放到 Agent Harness 里,它有两层含义。

第一层是新增约束。只有真实事故发生后,规则才进入 Harness。比如 Agent 曾经提交过被注释掉的测试,那下一版 Harness 不应该只在文档里提醒一句,而应该至少建立三道防线:

mermaid-02.png

图:一次真实失败会同时沉淀为规则、Hook 和评审标准。

第二层是删除过时约束。模型变强以后,一些旧 scaffolding 可能不再需要。比如早期为了防止模型在上下文剩余 30% 时焦虑收尾,很多 Harness 会写大量提醒;当新模型已经稳定处理这个问题,就应该删掉这些噪音。Harness 不是配置垃圾抽屉,不能只增不减。

所以 Ratchet 不是“规则越多越可靠”,而是“​每条规则都有事故来源​,并且经得起继续存在的测试”。这让 Harness 从提示词收藏夹变成一个会进化的工程系统。

状态外置:Filesystem、Git 与 Memory 的真实职责

很多人会把 Agent 的记忆想象成 Context Window。这个理解只对了一小半。Context 能承载当前轮对话,却不适合作为长任务的状态仓库。

真正可靠的状态通常在模型外面​。

Filesystem 是第一层外置状态。计划文件、调试日志、工具输出快照、子任务交接摘要,都应该写到磁盘上。这样即使上下文被压缩、会话被重启,任务仍然可以从文件恢复。

Git 是第二层外置状态。它提供版本化、分支、回滚和审计。Agent 在独立分支或 worktree 中探索,失败可以回滚,成功可以合并。对长任务来说,Git 不只是代码管理工具,还是 Agent 行为的安全带。

Memory Store 是第三层外置状态。AGENTS.mdCLAUDE.md 适合放静态规则,但不适合放动态事实。客户偏好、历史 bug 修复路径、某个服务的运行经验,更适合进入 KV、向量库或结构化 Markdown 树。它们解决的是“跨会话事实记忆”,而不是“本轮提示词补丁”。

再往外,还需要 Search 和 MCP 处理训练截止日之后的信息。模型知道的是过去,工程任务面对的是今天。把搜索、内部知识库、API 文档查询接进 Harness,是为了避免模型用旧事实回答新问题。

可以把状态外置理解成一条原则:凡是不应该随着 Context 被截断而消失的信息,都不要只放在 Context 里。

inline-02-external-state.png

图:长程任务的关键状态应沉淀到文件系统、Git 和记忆库,而不是只留在 Context 里。

Bash 与 Sandbox:通用能力必须配安全边界

设计工具层时,很多团队会先走向“为每种动作定义一个专用 Tool”。这条路很安全,也很快变得笨重。真实任务里的需求太碎:查日志、改 JSON、跑测试、抓网页、批量替换、临时分析数据。专用工具很难盖住所有场景。

因此,现代 Coding Agent 越来越倾向于给模型一个强通用工具:Bash。

Bash 的价值 不是“让模型执行命令”这么简单,而是让模型可以临时组合工具。rg 查找、jq 解析、curl 请求、语言测试 CLI 验证、headless browser 跑页面,这些动作不用每个都包装成独立 API。很多复杂任务最后都会坍缩成几条设计良好的 CLI 调用。

但 Bash 的风险也同样明显。给 Agent 通用执行能力,就必须同时给它安全边界:

能力开放对应边界
允许 Shell 执行危险命令拦截、审批流、命令审计
允许读写文件项目目录隔离、敏感路径 deny-list
允许联网默认禁出网,按域名或任务白名单开放
允许安装依赖临时容器、缓存隔离、依赖来源检查
允许并行任务worktree 隔离、文件锁、冲突检测

这里有一个重要的产品判断:默认工具链应该由 Harness 准备,而不是让模型临场配置。Git、ripgrep、语言测试工具、jqyq、浏览器自动化、包管理器,这些东西应该在执行环境里预装。模型不应该把时间花在“先装一个 rg”上。

通用工具让 Agent 能干,Sandbox 让它不至于失控​。两者缺一不可。

Hook Runtime:把偏好变成确定性纪律

Prompt 可以表达偏好,但不能保证执行。​Hook 的价值​,是把一部分“请记得”改成“系统会强制”。

在 Agent 生命周期里,Hook 可以插在多个关键点:

mermaid-03.png

图:Hook 在工具调用前后和任务结束前提供确定性校验。

Hook 的设计精神可以概括成一句话:​成功静默,失败喧哗​。

比如 Edit/Write 后自动跑 typecheck。通过时不返回任何信息,避免污染 Context;失败时把错误塞回下一轮,让模型必须修。这样你就不需要在 CLAUDE.md 里反复写“改完代码记得跑类型检查”。这条纪律已经从提示词变成运行时逻辑。

一个典型的 PostToolUse 配置可以长这样:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "cd $CLAUDE_PROJECT_DIR && pnpm tsc --noEmit 2>&1 | head -50"
          }
        ]
      }
    ]
  }
}

PreToolUse 可以拦截危险 Bash 命令,Stop Hook 可以在模型宣布完成前检查 todo、测试、未提交变更和交付物。Hook 的位置越靠近执行链路,规则就越不依赖模型“记性”。

这也是 Harness 和 Prompt 的分界:​能用确定性逻辑约束的,不要只写进提示词​。

Long-Horizon Execution:长任务靠外部状态与独立判定续航

长程任务是 Harness 能力的试金石​。单轮问答可以靠模型聪明,跑几十分钟甚至几小时的任务则会暴露三个问题:

  • Context 装不下完整过程,压缩会损失细节。
  • 模型容易把“当前阶段完成”误判为“整个任务完成”。
  • 子任务之间缺少交接协议,信息在 Agent 间漂移。

解决这些问题,需要同时处理 Context 和调度。

Context 侧有三种常用战术​。

第一是 Compaction,把旧上下文压缩成摘要。但压缩有损耗,连续压缩会让任务慢慢失真。更稳的做法是把关键状态写入 handoff 文件,然后在必要时重启会话,让新 Context 从磁盘恢复。

第二是 Tool-call Offloading。工具输出不应该无脑塞进 Context。几千行日志、完整网页、巨型目录树,都应该落盘;Context 里只保留摘要、路径和可继续查询的线索。模型需要细节时,再用 rgRead 精确取片段。

第三是 Progressive Disclosure。能力不应该全部预置进 System Prompt,而应该按需加载。Skill 体系的核心价值就在这里:启动时只暴露 name 和 description,命中任务后才加载完整说明、脚本和引用材料。

调度侧则要避免“自我宣布胜利”。可以用三种机制兜底:

调度机制解决的问题实现方式
Plan-then-Act防止任务在多轮执行中丢目标先写 plan 文件,完成一项勾一项,新增依赖及时追加
Ralph Loop防止模型过早退出Stop Hook 拦截 exit attempt,用干净 Context 重读状态继续
Planner / Generator / Evaluator 分离防止生成者自评偏乐观规划、执行、判定完成由不同角色或不同 Agent 承担

这里最重要的是 ​Evaluator 独立​。写代码的 Agent 往往天然倾向于说“已经好了”。更可靠的方式是在开工前定义 sprint contract:哪些测试必须通过、覆盖哪些边界、交付物长什么样。最后由独立 Judge 对照合同验收。

长任务不只需要更大的 Context,更需要更清晰的外部状态、更硬的退出条件和更独立的完成判定。

inline-03-long-horizon-loop.png

图:长任务要靠计划、执行和独立评估组成闭环,避免 Agent 过早宣布胜利。

Skill 与 Hook:普通工程师最容易动手的两个入口

如果把 Harness 拆到日常可操作层面,普通工程师最值得先抓两类东西:Skill 和 Hook。

Skill 解决“Agent 能做什么”。它把领域能力封装成按需加载的能力包,而不是把所有知识都塞进主提示词。一个好的 Skill,description 比正文更关键,因为它决定触发面:写得太窄,模型永远想不起它;写得太宽,它会在不该出现时污染任务。

Hook 解决“Agent 必须怎么做”。它把类型检查、危险命令拦截、提交前验证、结束前复核这些纪律放进运行链路。模型不需要记住纪律,Harness 会在对应时刻执行纪律。

两者分别对应 Harness 的两个方向:

入口本质典型问题
Skill能力的渐进式披露这个任务需要哪组领域知识、脚本和流程?
Hook纪律的确定性执行哪些规则不能依赖模型自觉?

每新建一个 Skill,都是在告诉 Agent“遇到这类任务时,你可以调用这组外部能力”。每挂一个 Hook,都是在告诉 Agent“到了这个生命周期点,这条规则必须执行”。这两类改动看起来像配置,实际上是在改 Agent 的运行时行为。

Harness-as-a-Service:外壳正在成为工业品

早期做 Agent,团队往往从 LLM completion API 开始,自己搭 loop、写 tool calling、维护上下文、做权限审批、补日志回放。每个团队都在重造同一套底座。

现在趋势变了。Claude Agent SDK、Codex SDK、OpenAI Agents SDK 这类产品,正在把 Harness 的一部分能力服务化:循环、工具注册、上下文管理、子 Agent、审批流、Trace,都逐渐变成可配置的运行时能力。

这就是 Harness-as-a-Service。它卖的不是模型,也不是传统 SaaS,而是一组默认工程决策:

  • Agent Loop 怎么跑?
  • 工具如何注册、过滤和裁剪结果?
  • 子 Agent 如何创建、交接和回收?
  • Context 什么时候压缩,什么时候重启?
  • 权限审批如何进入执行链路?
  • Trace 如何记录,失败如何回放?

HaaS 的价值是把 v0.1 成本摊平。团队不必从零写一个能跑的循环,而是拿到一个已经可用的底座,把精力放到领域 Harness 上:业务工具、项目规则、评测指标、数据权限和组织知识。

但这不意味着 Harness 会消失。模型变强后,旧约束会减少,新的约束会出现。过去我们不信任模型记得跑测试,于是写 Hook;未来我们可能不信任模型能协调多个 Agent 同改一个仓库,于是写 worktree 调度和冲突仲裁。Harness 不会变薄,它只会把边界移动到更高层。

结语

Agent 的能力上限由模型决定,但实际表现往往由 Harness 决定。​模型是发动机,Harness 是传动、刹车、仪表盘、导航和安全笼​。发动机再强,如果外壳不能把动力稳定传到真实任务上,结果依然会失控。

对普通工程师来说,这反而是好消息。模型训练不在多数团队手里,但 Harness 在。你可以今天就改一条工具描述、删掉一条过时规则、把长日志落盘、给任务加 plan 文件、把类型检查搬进 Hook、把领域流程封装成 Skill。

这些动作看起来细碎,却共同决定 Agent 是否能从“会回答”走向“能交付”。所谓 Harness Engineering,就是承认模型之外还有一整套工程系统,并且认真打磨它。

模型由实验室推进,Harness 由工程现场推进。真正能让 Agent 在具体任务里变可靠的,往往正是这层“模型之外的全部”。