☰
Agent工程真相:循环背后的安全与效率博弈
2026/10/7 14:12:43 网站建设 项目流程

做一个 Coding Agent,接上模型,再给它几个工具,看起来就可以开始干活了。

那为什么同一个模型,放在不同的 Agent 里,效果会有差别?为什么一个简单的循环就能完成一些编程任务,做成产品以后,却又需要上下文压缩、权限、会话恢复和多 Agent?这些能力到底要做到什么程度?

我觉得,讨论 Agent 工程,绕不开这几个问题。模型决定了很多事情,但开发者交付的是一套能够运行的系统。模型发起工具调用以后,谁来执行,失败了怎么办,下一轮给模型看什么,都要由这套系统处理。

这部分通常叫 Harness,可以理解为 Agent 的运行框架。

今天聊一篇专门拆解 Harness 的研究。它检查了 Claude Code、Codex CLI、Gemini CLI、Mistral Vibe、OpenHands、Aider、Mini-SWE-Agent、Hermes、Pi、OpenCode 和 OpenClaw 的源码。论文篇幅很长,我挑几个做 Agent 时会遇到的问题展开,看看这些系统具体怎么处理,也说说我的判断。

目录

  • 一、一个 while 循环,能做多少事情?
  • 二、都是调用模型,为什么还要单独做一层?
  • 三、工具多了,是更强,还是更难选?
  • 四、上下文压缩,到底要留下什么?
  • 五、找代码,需要先建向量索引吗?
  • 六、同意执行命令以后,还需要沙箱吗?
  • 七、多 Agent 能省多少时间?先看任务怎么分
  • 八、从头做一个 Harness,我会先补哪些能力?

一、一个 while 循环,能做多少事情?

先看一个任务:用户要求给现有项目加一个 CSV 导出功能。导出时,要沿用当前页面的筛选条件,不能影响原有分页。

Agent 需要找到页面和接口,读代码,决定改哪里,完成修改,再运行验证。中间可能发现接口返回的数据不够,也可能遇到类型错误。下一步怎么走,要根据前一步的结果决定。

基础循环大概就是这样:

接收目标和上下文 → 调用模型 → 执行模型请求的工具 → 把结果补充到上下文 → 再次调用模型 → 继续执行,或结束任务

图 2|行动与观察循环、加入检查反馈的循环,以及协调者与工作者结构。

图中间加入了检查步骤。比如完成导出功能的修改以后,运行测试,把失败信息返回给模型继续修正。Aider 的编辑与检查流程就属于这一类。右边则增加协调者,让工作者返回结果以后再决定下一步。几种结构的差异在于怎么组织反馈和分工,底层仍然需要执行工具、处理结果。

这个结构本身并不复杂。Mini-SWE-Agent 就是一个比较直观的例子:围绕简单循环,通过 shell 与环境交互。它的设计便于观察执行轨迹,也便于研究模型在各轮做了什么。

但把这个循环变成开发者每天使用的产品,还会遇到其他问题。

比如模型请求执行测试,测试进程一直没有退出;用户点击停止,后台命令还在跑;工具返回几万行日志,下一次模型请求直接超出上下文限制;模型连续执行同一条命令,结果没有变化,却迟迟不结束。

这些问题要在模型调用之外处理。运行系统要处理最大轮数、超时、取消、输出长度和预算限制。

图 1|Harness 的组成:模型接入、执行循环、工具、上下文、安全、编排和扩展。界面层可以是终端、IDE、SDK 或服务。

我会按图里的七个部分检查一个 Agent。

部分

要回答的问题

循环

下一步怎么推进,什么时候停止

模型接入

模型协议、工具调用、流式输出和重试怎么处理

工具

能读什么、改什么、执行什么,结果怎么返回

上下文

本轮需要哪些信息,历史太长怎么办

安全

操作是否允许,执行环境限制在哪里

编排

子任务怎么分,结果怎么汇总

扩展

技能、外部工具和其他系统怎么接入

简单循环的好处是容易理解。每轮发生什么,顺着代码就能看清楚。功能多了以后,也容易把各种判断都塞进循环:执行前检查权限,调用前检查预算,返回后检查上下文,结束前检查状态。

Mistral Vibe 提供了另一种做法,把多种运行策略组织成中间件。上下文压缩之类的逻辑,可以从主循环中拆出来,在每轮执行时单独处理。

这个取舍,我觉得要看功能是否已经互相影响。如果只有几项检查,直接写进循环未必有什么问题。预算、压缩、只读模式、审计等规则需要各自调整时,拆成独立模块会更容易维护。

循环复杂程度不能直接代表 Agent 能力。简单循环也可以完成复杂任务;复杂运行系统通常还要满足交互、恢复、安全和扩展要求。比较的时候,先问它解决的是哪一类问题。

再看几个系统,循环里的实现差异会更清楚。

OpenHands 更重视执行记录和会话结构。它把事件写入持久化日志,模型看到的历史,则由当前会话分支上的事件整理而来。会话可以形成树,支持回放和分叉。一次模型响应里如果包含多个工具调用,系统可以将它们作为一批动作执行。

并行时,它还按工具声明的资源加锁。比如两个调用都操作同一个文件,就需要串行;读取不同文件,则有机会同时执行。这比简单地判断“读工具能并行,写工具不能并行”更细,但工具需要准确声明使用的资源,运行系统也需要维护锁。

放到 CSV 导出任务里,前端和后端位于不同文件,可以分别操作;两个工作者同时修改同一份接口定义,就要协调。并行调度需要知道它们实际碰到了什么资源。

Claude Code 按工具是否适合并发来分批执行。工具默认不允许并行,确认安全的读取、搜索等操作才进入并行批次,写入和命令执行采用更保守的处理。

和 OpenHands 的资源锁相比,这种方式依赖工具类别判断,实现更直接。并行读取可以减少等待,但两项互不相关的写入操作,也可能因为类别限制而无法同时执行。并发粒度不同,速度和实现成本的取舍也不同。

Codex 用异步状态机组织会话。Codex 的 Rust 实现围绕 Session 处理轮次、模型流式事件和工具执行,再向外提供事件读取接口。工具调用也有专门的运行层。

这种结构适合把“任务已经接受”“模型正在输出”“工具正在执行”“轮次结束”分别传给界面和上层调用方。Agent 作为服务或 SDK 使用时,持续反馈这些状态,比等任务结束后只返回一段文字更有用。

Pi 的核心循环比较精简,也专门处理了用户中途发来的消息。它区分 steering 和 follow-up 两种队列:前者在当前轮工具执行完后交给模型处理,后者在 Agent 原本准备停止时再处理。

比如导出功能做到一半,用户补充“只导出选中的记录”,这是对当前任务的调整;用户又说“做完以后帮我检查另一个页面”,则可以等待当前任务结束。运行系统区分这两类输入,系统就能区分哪些消息需要立即调整当前任务,哪些可以稍后处理。

Pi 还有一个具体保护:如果模型响应因为长度限制被截断,本轮工具调用不执行。参数即使勉强能解析,也可能不完整。这个判断发生在执行之前,避免在修改内容只输出了一部分时就操作文件。

OpenCode 把部分待处理工作写进消息日志。启动子 Agent、压缩上下文等待办动作,会作为消息的一部分写入日志,再由循环逐项取出处理。进程重启后,可以继续识别日志里的待处理工作。

不过,能恢复队列,不代表所有有副作用的工具操作都可以安全重试。文件已经改了但结果没有记录的情况,仍然需要结合工具的实际结果和执行记录判断。

这几种实现各有侧重:OpenHands 围绕事件组织会话,Codex 用异步状态机调度,Pi 细分用户输入,OpenCode 则把待办动作写进日志。选择的时候,我觉得可以先明确产品需要哪一种行为,再决定循环里要增加什么。

二、都是调用模型,为什么还要单独做一层?

模型接入容易被低估。第一版通常只要调用一个接口,把消息和工具定义传过去,再解析返回值。

支持第二个模型以后,事情就开始变多。工具调用格式可能不同,推理内容的返回方式可能不同,模型支持的参数也不同。切换模型时,历史里的消息能不能继续用,同样需要处理。

拿前面的 CSV 导出任务来说。模型输出一个工具调用,系统接收的是流式数据。如果参数还没传完,就开始解析和执行,容易把不完整的内容当作正式输入。模型接入层需要知道什么时候一次工具调用的信息已经接收完整,工具执行层再做参数校验。

请求失败也需要分类。限流可以等待后重试;上下文过长,需要减少输入;参数不支持,需要调整请求。所有错误都原样重试,容易造成无效等待。

论文里有几种不同安排。厂商自己的 Agent 通常会更多利用自家模型能力;支持多家模型的系统,则需要维护兼容信息和转换逻辑。Pi、Hermes、OpenCode 都有各自的兼容处理,提示词缓存也是其中一部分。

我觉得这层抽象至少要让上面的循环知道:这次模型请求是否成功,拿到了哪些工具调用,用了多少 token,失败属于哪一类。至于具体厂商的格式差异,尽量集中处理。

缓存也有类似问题。提示词内容差不多,并不意味着服务一定能复用。稳定前缀、消息排列和厂商支持的缓存标记,都可能影响结果。

例如每轮都在系统提示词开头插入最新时间或变化的项目状态,前缀就会变化。把固定规则和基础工具放在相对稳定的位置,把动态信息放到后面,更便于利用缓存。最终有没有减少成本,还要看实际返回的用量。

把这些逻辑集中到模型接入层,后续接入新模型,就有明确的地方处理兼容问题。如果每个 Agent 都自己写一份,同类问题就要在多处修复。

模型兼容也会直接影响工具接口。OpenCode 会根据模型切换编辑方式:GPT 系列模型使用移植自 Codex 的 apply_patch 补丁格式,其他模型使用另一套编辑接口。

这个做法说明,同一个运行系统可以给不同模型提供不同的工具接口。需要付出的成本是维护这些分支,并分别验证编辑成功情况。统一成一套接口更容易维护,按模型适配则可能更容易利用模型已经熟悉的形式。

所以我会把“支持某个模型”拆开看。请求能否成功是一层,能够稳定生成工具参数是另一层,缓存、上下文和编辑接口能否适配,还要继续验证。

三、工具多了,是更强,还是更难选?

同样是修改代码,Agent 可以只用 shell,也可以使用专门的读取、搜索、编辑和执行工具。

图 3|各系统的工具数量与编辑形式。

工具少,接口简单,模型需要通过组合命令完成更多事情。工具多,可以直接表达常见操作,但每个工具都有参数和说明,模型要先理解,再选择。

所以我更关心工具的输入输出是不是清楚。

举例来说,Agent 要把导出按钮的处理函数改掉。一个编辑工具要求传文件路径、旧文本和新文本,旧文本必须在文件中只匹配一处。匹配不到就返回错误,匹配多处也返回错误。

这种方式有一个好处:模型如果记错了文件内容,工具会明确拒绝。它需要重新读文件,找到正确位置,再修改。

如果改成模糊匹配,就要处理另一类问题:看起来相似的两段代码,工具选了哪一段?空格或缩进变化可以容忍到什么程度?修改完以后,怎样确认没有改错位置?

几个系统在这里有不同选择。Claude Code 等采用较严格的编辑约定;Aider 有从精确到模糊的匹配处理;Gemini CLI 的实现中,还出现了通过额外模型调用修复编辑匹配的方式。

我觉得这个差异很具体:严格匹配更容易发现文件状态不一致,但可能需要模型多读一次;宽松匹配能容忍部分输出偏差,但要承担错误定位和验证成本。没有一个匹配规则适合所有模型和任务。

再展开几个编辑实现。

Codex 使用自己的补丁格式。apply_patch 通过补丁块描述文件的新增、修改或删除。这类接口适合一次表达多处变化,工具需要负责验证补丁能否应用,不能只确认格式能解析。

Pi 会统一处理 Unicode 和空白差异,但不依赖相似度阈值来放宽匹配。它处理 Unicode 和空白差异,检查是否匹配到多个位置,以及多次编辑的范围是否重叠,并在执行前生成 diff 预览。目标是容忍格式差异,同时保留精确修改的约束。

Hermes 和 OpenCode 更强调匹配容错。它们有多阶段匹配处理,依次尝试精确匹配,以及处理空白、缩进和上下文差异。Hermes 还区分验证和应用,OpenCode 会把 LSP 诊断补充进编辑结果。

拿两个都叫 handleExport 的函数来说,宽松匹配不能只回答“找到相似内容了”,还要处理是否存在多个候选。修改后补充诊断,能够让模型及时看到类型或语法问题,但诊断没有报错,也不代表业务行为正确。

Mistral Vibe 的变化也值得看。论文保留了前后两个时间点的源码。在这段时间里,它移除了原先的模糊 SEARCH/REPLACE 方式,转向精确且唯一的旧文本匹配。对有歧义的编辑,返回工具错误。

这个变化说明,编辑接口的设计也会调整。随着模型和任务环境变化,工具承担多少修复工作,也可能调整。自建 Harness 时,编辑成功率、错误位置和额外调用成本,需要一起观察。

工具返回也要说清楚。搜索没有结果,与搜索输出被截断,是两回事。命令退出失败,与命令还在运行,也是两回事。模型下一轮能不能处理好,取决于它有没有拿到这些状态。

再说工具规模。接入多个 MCP 服务以后,工具定义本身就会占用上下文。即使本轮只是改一个按钮,模型也可能收到一大批完全无关的工具说明。

按需发现和加载,可以减少这部分开销。Claude Code 提供延迟加载和工具搜索;Codex 有基于 BM25 的工具搜索;Hermes 会在工具目录占用过多上下文时,通过搜索、描述和调用三个入口访问目录。

以一个包含代码、文档、工单和发布能力的平台为例,当前任务只涉及代码,可以先提供基础开发工具。如果用户要求关联工单,再检索相关工具定义。这样模型不必每轮阅读完整目录。

但这里也有代价。模型要多做一次工具搜索,目录描述也要足够准确。工具已经安装,却始终搜不到,延迟加载就没有解决任务问题。因此我会同时检查上下文成本和能否找到所需工具。

外部接口也不必一对一全部暴露。为了回答“这个需求现在卡在哪里”,模型可能要查询需求、任务、负责人和状态。固定的数据查询和关联可以由程序完成,工具返回整理好的结果。模型再决定是否需要继续查细节。

四、上下文压缩,到底要留下什么?

继续看 CSV 导出任务。用户一开始明确要求“沿用当前筛选条件,不影响分页”。Agent 读了代码,查了依赖,运行测试,又处理了一次编译失败。

如果历史太长,系统把它压缩成“正在实现 CSV 导出”,信息就不够了。接下来模型可能生成一个能下载的文件,却把筛选条件忘了。

这个例子说明,压缩需要保留任务状态。只概括讨论过哪些主题,很难支持后续执行。

图 4|四种上下文管理方式:完整历史、递归摘要、上下文压缩器和阈值压缩。

Aider 的做法比较容易理解:历史消息超出 token 预算以后,把较早的一部分交给模型总结,保留最近的原始消息。如果摘要加上近期消息后仍然过长,就继续处理。

这里保留近期消息有实际用途。正在修复编译错误时,最近的错误堆栈和代码,比一句“编译失败,正在处理”更有用。

OpenHands 把这部分做成可插拔的 Condenser。早期提供的压缩器种类较多,后来的 SDK 精简了实现,同时把压缩请求和结果也放进事件记录。

这有助于排查一个具体问题:Agent 到底是在压缩之前忘了筛选条件,还是压缩结果漏掉了它?如果压缩本身留下记录,就能比较前后状态。上下文超限或历史格式出错时,也可以转入压缩处理,而不一定直接结束会话。

其他系统会按 token 阈值触发压缩。Gemini CLI 会生成状态快照并保留近期历史;Mistral Vibe 会把之前的用户消息重新放进压缩后的上下文;Pi 和 OpenCode 等实现会利用已有摘要继续合并信息。

这几种做法分别解决不同问题:保留当前调试现场,让原始目标继续存在,避免多次压缩后丢掉早期决策。

我会让摘要至少回答这些问题:任务是什么,约束有哪些,已经改了哪些文件,做过什么验证,目前卡在哪里,接下来准备做什么。

对于导出功能,摘要里应该明确保留“筛选条件要进入导出请求,分页行为保持不变”。如果已经发现后端导出接口不支持某个筛选字段,也要写进去。否则模型后面可能重复采用已经排除的方案。

触发压缩时,也要为后续消息预留空间。当前请求没有超限,但工具马上返回一份大文件,下一轮就可能超限。历史预算和工具输出预算需要一起安排。

较长的内容可以存到外部文件或存储中,只把存储位置和必要片段返回给模型。Agent 需要时再读取。完整记录仍然保存,模型本轮不必全部看到。这样既能控制上下文,也能在排障时回到原始数据。

这几种实现里,还有一些与会话恢复直接相关的做法。

Hermes 在压缩时保留会话之间的关联。压缩时结束当前 SQLite 会话,再创建通过 parent_session_id 关联的子会话。新的上下文变短,之前的记录仍然保留,可以沿着父子关系查询。

如果一个导出任务经历过三次压缩,就不会只剩下最后一份摘要。排查时可以回到每段会话,检查早期要求和修改过程。这需要维护会话关系,查询历史时也要处理跨会话的重复信息。

Pi 的会话底层是追加式 JSONL 树。每条记录有自己的标识和父标识,用一个指针标记当前会话所在的分支。分叉、回看和探索另一条路径,可以建立在同一份结构上。读过和改过的文件清单,也能跨压缩保留。

这里有个边界:切换会话分支,并不自动恢复文件系统。模型的对话历史回到了之前的状态,磁盘上的代码可能已经发生变化。产品如果提供回退,就要说清楚回退的是对话,还是对话和文件一起恢复。

OpenCode 的摘要按固定栏目记录任务状态。它把之前的摘要再交给压缩 Agent,按目标、重要细节、工作状态、下一步和相关文件合并。仍然成立的信息保留,过期的信息移除。

对长开发任务,这类结构化摘要比一段泛泛的对话概括更容易核对。导出接口已经确认,测试还没通过,就可以分别写进“工作状态”和“下一步”。

上下文压缩和持久化记录要分开。摘要用于下一轮推理,完整事件记录用于恢复、回放和审计。不能因为模型暂时不需要一段日志,就把唯一的执行证据删掉。

长期记忆同样需要区分。项目的测试命令可以长期保存;一次测试失败和临时推测,应该留在当前任务里。否则下次任务可能继承已经失效的信息。

五、找代码,需要先建向量索引吗?

对 Coding Agent,我会先用好代码本身的结构。

用户要求增加 CSV 导出,入口可能是当前页面的组件名、筛选字段或请求路径。找到页面后,再沿着事件函数、请求封装和后端接口继续读。每一步的结果都能在文件里确认。

ripgrep、glob、语法树和语言服务,可以覆盖很多这样的定位工作。它们返回路径、行号或符号,方便 Agent 继续操作,也能反映当前工作目录里的变化。

这篇研究的一个观察是,样本没有采用向量嵌入检索代码。我觉得可以据此调整起步顺序:先验证文本和结构检索能做到什么,再判断是否需要额外索引。

代码定位、对话记忆和跨文档查询的需求不同。OpenClaw 等系统在记忆检索中仍然使用向量检索,需要根据具体任务选择。

如果要增加语义检索,我会拿一组现有搜索难以处理的任务来比较。例如用户只给出业务描述,没有明显关键词,语义检索是否更容易找到相关代码?找到以后是否减少了后续读取?索引维护和同步增加了多少成本?这些都可以具体测。

项目规则文件也属于上下文来源。AGENTS.md、CLAUDE.md 等文件,负责提供代码里不容易直接看出的要求。

例如根目录说明通用的测试和格式规范,某个子目录要求兼容旧接口。Agent 修改这个目录之前,需要加载对应规则。把全部目录说明一次性加载进来,会增加输入;按需加载,则必须保证规则在修改之前生效。

研究中的多个系统都在采用这种分层 Markdown 上下文。它的优势是规则放在项目里,可以检查和维护。实际实现还要说清楚加载范围、覆盖关系和更新时间,避免模型拿着旧规则工作。

Aider 的 RepoMap 也可以单独看。它通过 tree-sitter 提取代码符号,根据当前对话对相关信息排序,在有限的 token 预算内生成仓库地图。

比如当前讨论导出接口,仓库地图可以帮助模型先看到相关符号和结构,再读取具体文件。它与直接全文搜索提供的是不同信息:一个帮助了解代码关系,一个帮助定位具体内容。最终修改仍要回到实际文件。

另外,代码上下文和长期记忆也别混在一起。几个系统在“谁负责把信息写成记忆”上,选择很不一样。

Codex 采用后台提取和整理。研究中的记忆流水线先从会话记录提取信息,再由受限制的内部 Agent 整理成文件。整理过程没有网络访问,只允许指定范围内的本地写入,并对变化进行检查。

Gemini CLI 增加了人的审核。它会从已结束的会话中提取信息,生成补丁,放到项目的 inbox,用户审阅以后再采用。这样记忆形成得慢一些,但哪些内容会成为后续任务的依据,需要经过用户确认。

Hermes 使用有大小限制的 Markdown 记忆文件。会话开始时加载一份记忆,在本次会话中保持不变;后续更新写入磁盘,不立即改动固定的提示词前缀。查询历史记录则依赖 SQLite 全文搜索,也可以通过插件接入向量检索。

OpenClaw 的比较重点不同。它更接近多渠道助理网关,论文的文件编辑表也没有把它列为专门的代码编辑器。把它纳入样本,是为了比较通用 Agent 的会话、记忆和扩展结构。

它的 Active Memory 插件可以在主 Agent 回复之前运行专门负责记忆检索的子 Agent,补充相关信息。这里要解决的是“这次回答需要哪些历史记忆”,与 Coding Agent 查函数定义并不相同。多渠道和长期使用,还会带来会话路由、记忆范围与工具可见性的问题。

我觉得长期记忆的设计需要先回答一个问题:什么信息可以变成未来任务的默认依据?“这个仓库的导出接口不支持某个字段”也许值得保存,但接口升级以后就需要更新;一次临时猜测,不能因为写入了记忆就变成项目事实。

六、同意执行命令以后,还需要沙箱吗?

需要。操作授权和执行隔离解决的是不同问题。

用户同意 Agent 运行测试,允许的是这次测试操作。测试进程实际能读取哪些文件、访问哪些地址、启动什么子进程,还要由执行环境限制。

拿导出功能来说,Agent 修改完代码,要运行项目测试。测试脚本可能调用其他程序。权限层判断允许执行测试,并不自动保证这些程序只能访问项目目录。

图 5|论文中的 Claude Code 权限设计:静态 Hook 规则、模型权限分类、用户确认对话。

分层判断的思路是,让不同规则处理不同情况。可写路径、工具限制等明确要求,可以由程序检查;项目自己的校验可以接到 Hook;需要理解意图的操作,可以交给模型辅助判断或请求用户确认。

但模型判断允许执行之后,执行环境仍然需要自己的边界。尤其是共享、托管或无人值守任务,文件系统、网络和进程的访问范围都要单独配置。

授权范围也要具体。允许修改当前项目,不等于允许修改上级目录。用户同意了一条命令,后面参数发生变化,也要判断原授权是否适用。

取消是另一个需要检查的地方。用户点停止以后,模型请求停了,工具是否停了?工具停了,子进程是否还在?多 Agent 情况下,工作者是否继续执行?界面显示“已停止”时,实际执行也应该已经结束。

还有会话恢复。某次写入已经完成,但执行结果还没保存,运行实例就退出了。恢复以后,如果系统直接重试,可能产生重复副作用。

因此会话里需要记录执行状态,区分调用已发起、正在执行、已返回结果。对有副作用的工具,再根据具体场景处理幂等、状态查询和人工介入。不能只保存一份聊天历史,就认为恢复完成了。

我觉得权限、隔离、执行记录和恢复需要一起设计。它们最后都要回答:这次动作到底能不能做,已经做了没有,出问题以后怎么接着处理。

七、多 Agent 能省多少时间?先看任务怎么分

任务长,不一定适合多 Agent。

例如 CSV 导出功能,前端和后端都要改。如果两边还没有确认接口参数,各自启动一个 Agent 同时写,最后可能对不上。先确定参数和返回格式,再让双方在独立目录里实现,才有并行空间。

如果只有一处小改动,启动多个 Agent 还需要传递任务、等待结果和合并修改,可能比一个 Agent 直接处理更慢。

图 6|单 Agent、顺序委派、并行子会话、层级线程树、递归组合,以及注册表与协议。实线表示启动或调用,虚线表示返回。

我觉得多 Agent 有两类比较清楚的用途。

第一类是独立任务并行推进。接口约定已经明确,前端、后端和测试可以分别处理。但要安排工作目录和合并,避免同时覆盖文件。各自通过测试以后,合在一起仍然需要集成验证。

第二类是分开处理信息。一个工作者调查导出接口,另一个检查筛选逻辑,主 Agent 拿到结果后决定怎么改。主会话不用装入每一次搜索和所有文件内容,可以继续保留总体目标。

这两类收益也对应两类成本:并行需要协调修改,分开处理信息,需要保证汇总结果准确、完整。

子任务应该写到什么程度?我会明确目标、范围、工具和返回内容。例如“检查后端导出接口支持的筛选参数,返回文件位置、参数定义和不支持的条件,暂不修改代码”。主 Agent 拿到这份结果,就能继续决定接口方案。

如果只说“帮我看看后端”,工作者不知道查到哪里算结束,主 Agent 也很难判断报告是否够用。

图 7|协调者模式:工作者按工具白名单执行任务,再向协调者返回结果。

这个实现里,我更关注工具白名单。调查任务可以限制在读取和搜索,实施任务再提供编辑与执行能力。主 Agent 可以按阶段委派,避免所有工作者从一开始就修改环境。

研究中的协调流程包括调查、汇总、实施和验证。对应到导出任务,就是先查前后端现状,确认接口,再分派修改,最后验证筛选、分页和下载结果。这样每个阶段的输入和结束条件都比较明确。

工作者的结果也要能核对。哪些结论来自代码,哪些是推测,有没有运行验证,都应该写清楚。几个工作者意见不一致,主 Agent 需要回到文件或环境里确认,不能只是把报告拼在一起。

再看几个系统具体怎么委派,差异就更明显了。

Codex 给工作者独立的线程标识和历史。父子线程通过类型化消息交互,上下文继承可以选择完整历史或最近若干轮。这样既能让工作者接续已有调查,也能限制它看到的信息。

Mistral Vibe 的委派是顺序执行。父 Agent 通过 task 工具启动新的 AgentLoop,等待子任务完成,再接收事件和结果。它限制可以委派的角色,避免随意递归到更宽权限的配置。

所以看到“支持子 Agent”,不能马上理解成“能够并行加速”。顺序委派的价值更多是分开任务和上下文,父任务仍然要等待。

Gemini CLI 使用 Agent 注册表。定义可以来自用户、项目和扩展;本地与远程执行通过统一的会话接口组织,并有实验性的 A2A 服务。在企业里,如果某个检查能力已经是远程 Agent,可以沿这个边界接入,不一定把所有执行代码放进同一进程。

Hermes 对子任务权限采用交集。工作者的工具集合不能超过父任务已有的能力,并额外限制递归、用户交互、记忆写入等操作。默认委派比较保守,也提供通过配置启用的编排和共享数据库协作方式。

Pi 把子 Agent 放在扩展里。核心没有 spawn 工具,示例扩展通过启动独立 Pi 进程执行子任务,解析 JSONL 输出,汇总进展和成本。上下文因此天然分开,父子通信、资源管理则要由扩展处理。

OpenHands 用独立会话组织工作者,OpenCode 用子会话。前者为工作者建立独立事件记录,把摘要和指标带回父任务;后者沿用已有会话引擎,再根据权限配置收窄子会话能力。

它们共同需要解决目标、上下文、权限和结果返回,但分别通过线程、进程、注册表或会话实现。对于自建平台,我会先明确需要哪一种隔离和恢复,再挑合适的委派方式。

主任务的预算和取消,也要传到子任务。父任务已经停止,子任务不能继续消耗资源或改代码。

几个系统还在“怎么发现任务没有进展”上做了不同安排。

OpenHands 检查重复动作与观察、反复报错、交替模式等多类情况。Gemini CLI 先通过哈希比对发现重复内容和工具调用,执行轮数较多时,再让模型自检。两者都在尝试识别无效的重复执行,但维护成本和误判情况需要分别验证。

OpenCode 连续遇到相同调用时,会进入权限确认,让用户决定是否继续。Hermes 的默认设置更偏向把重复失败警告写回工具结果;它另有 verify-on-stop 检查,可以在修改代码后缺少新的验证证据时,要求继续执行。

这个停止检查比较具体:Agent 不需要每一轮都做一遍完整反思,而是在准备结束时检查是否缺少验证。对于导出任务,代码已经改了,但没有新的测试或检查结果,就还有事情没做完。

这类机制仍然不能替代验收。执行过测试,只能证明有验证动作;筛选条件是否生效、导出数据是否正确,需要相应检查覆盖。

八、从头做一个 Harness,我会先补哪些能力?

看完这些实现,我觉得可以先把一个任务的完整过程做扎实。

1. 能查清执行过程的循环。模型输入、工具参数、执行结果和结束原因,都能按会话查到。先给出轮数、时间和预算限制,避免失败后无限重试。

2. 一组能重复运行的任务。既有正常修改,也有文件找不到、编辑歧义、测试失败、工具超时和用户中断。每项能力加进去以后,都用这些任务重新检查。

3. 长任务的上下文处理。大输出按需读取,摘要保留目标和进度,近期调试信息保留原文。再验证压缩和恢复后,Agent 是否继续遵守原来的要求。

4. 实际产物验证。导出任务不能只看测试命令退出成功,还要检查筛选有没有生效、分页有没有变化、CSV 内容是否正确。运行状态和用户验收要分别判断。

5. 权限和执行环境。可写范围、网络限制、授权与取消,都需要落到代码和配置中。有副作用的操作,考虑失败后怎样确认执行状态。

这些基础能力做好以后,再按需要接入技能、MCP 或多 Agent。技能适合保存常用流程与领域知识,MCP 解决外部工具连接,ACP 可以用于编辑器交互或 Harness 互操作。具体协议要看系统边界,内部委派未必需要跨系统协议。

这篇研究还提供了一个约 90 行的最小 Harness 示例。它适合看清循环、工具和上下文如何连在一起。用于真实项目,还要补齐模型兼容、输入校验、预算、权限、取消和恢复,不能把示例行数当成完整产品的实现成本。

为了方便对照,我把这 11 个系统在本文涉及的特点放在一起。

系统

本文重点展开的实现

Mini-SWE-Agent

简单线性循环、shell 工具、执行时间与连续错误次数限制

Aider

编辑后检查与修正、多种编辑格式、RepoMap

Claude Code

工具并发分类、延迟加载、协调者与工作者

Codex CLI

异步会话状态机、补丁工具、线程委派、后台记忆整理

Gemini CLI

调度与循环检查、模型编辑修复、上下文处理、注册表与远程 Agent

Mistral Vibe

每轮执行前的中间件检查、精确编辑、压缩后保留用户目标、顺序委派

OpenHands

持久事件日志、资源锁、可插拔压缩、独立工作者会话

Hermes

停止前验证、编辑容错、父子会话关联、受限委派

Pi

用户输入队列、响应截断保护、会话树、扩展式子 Agent

OpenCode

日志兼作队列、按模型选择编辑接口、增量摘要、子会话

OpenClaw

多渠道助理网关、回复前检索记忆、通用 Agent 扩展

这个对照还有一个用途:避免把所有功能都列成自建系统的第一版需求。一个单人使用的代码工具,未必需要复杂会话路由;企业托管平台,却要优先考虑运行状态、权限和恢复。目标不同,先补的能力也不同。

最后说一下我会怎么衡量改动。一个新能力加进去,任务是否更容易完成,失败位置有没有变化,耗时和成本增加多少,人需要介入几次,都要记录。

比如加入子 Agent,前后端修改同时推进了,但合并冲突增加,整体耗时未必下降。摘要减少了 token,如果同时丢掉筛选约束,产物就不符合要求。工具增加了,如果模型选择错误,还是会走弯路。

这些都需要回到执行记录里看。对于前面的 CSV 导出,我会检查 Agent 有没有读到必要代码、确认接口、保留约束、正确修改并验证结果。哪一步出了问题,就改对应的运行环节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询