Harness 系统级拆解:从“控制权分配”看 Agent 运行时的六种形态
一、引言:当 Harness 成为能力的实际边界
2026 年做 Agent 的开发者,大多经历过同一种挫败感:换一个模型,工具注册要重写一遍;换一个场景,主循环要重写一遍;想给会话加断点续跑,发现状态散落在七个模块里,谁也不敢动。每个团队都在重复发明同一套东西——Loop、工具调度、上下文管理、Session、沙箱——而且发明得都不太一样,也都不太能复用。
与此同时,行业悄悄形成了一个共识:模型决定能力上限,Harness 决定实际下限。 同一个旗舰模型,接在一个粗糙的循环里表现平平,接在一个精心设计的 Harness 里——上下文管理得当、工具返回处理细腻、失败有重试策略——立刻脱胎换骨。各家 benchmark 报告里越来越常见的一个脚注是:“成绩依赖于所用 Harness”。
Harness 不是模型,也不是某个具体的 Agent 应用,而是介于两者之间的那一整圈运行时:主循环怎么转、工具怎么注册与调度、上下文怎么组装与裁剪、会话怎么持久化、代码在哪个沙箱里跑、失败了怎么恢复。它是一层“底座”,而不是一层“胶水”。
本文拆解六种代表性的 Harness——DeepSeek Harness(DSH)、OpenAI Codex、Claude Code、OpenCode、Pi、Hermes Agent。不比较模型能力,只比较运行时架构。核心问题是:一个 Agent Harness 在架构层面究竟要回答哪些问题?不同答案如何塑造了产品的形态、能力边界,以及它实际把哪些工程责任留给了使用者。
二、分析框架:五条第一性轴线
与其罗列特性,不如先立一套坐标系。Harness 的几乎所有架构差异,最终都可以追溯到同一条轴线的不同位置:框架把多少运行时控制权留在内核,又把多少交给插件与调用者。
轴线一:Agent Loop 的控制权归谁。 Loop 是 Harness 的心脏。它是硬编码的固定状态机,还是可被插件替换、拦截的编排层?这决定了外部开发者能在多深的层次上改变 Agent 的行为节奏,也决定了一个框架是“产品”还是“底座”。
轴线二:扩展以什么为载体。 是 shell 脚本 Hook、TypeScript 插件模块、微内核服务注入,还是编程式运行时?载体不仅决定了扩展的表达力和类型安全程度,更决定了扩展与核心循环的耦合深度——是在循环外面挂东西,还是能伸手进循环里面改逻辑。
轴线三:状态的所有权与生命周期粒度。 状态是会话级黑盒,还是被划分为 Thread / Turn / Step 等显式层级,或者分散在插件各自的 Context 中?粒度决定了可恢复性、可审计性与并发能力,也决定了“状态放错位置”这类隐蔽 Bug 的出现概率。
轴线四:安全边界的实现位置。 沙箱是内建在引擎内核中的系统级硬隔离,是可替换的插件,还是根本不存在、把责任交还给调用者?这直接影响 Harness 能否安全地执行不可信代码,以及安全责任最终落在谁的头上。
轴线五:运行时的“自重”与成本结构。 系统提示有多大、启动与调用的固定开销是多少、是否假定长期驻留的内存与状态?自重决定了它在高频、轻量、预算敏感场景中的可用性,也决定了它是为“短任务”还是为“长期陪伴”而设计的。
没有一条轴线存在“绝对最优”。每一条上的选择都是权衡。六种 Harness 的差异,本质上是同一组权衡的不同落点。接下来的拆解,将逐一考察每个系统在这五条轴线上实际站到了什么位置,以及那个位置是否与它的宣称目标自洽。
三、逐个拆解:六种形态,六种控制权分配
3.1 DSH:把控制权彻底下放给插件树
DSH 的架构宣言只有一句:一切皆插件。模型适配器、工具注册表、会话日志、沙箱、文件系统,乃至 Agent 主循环本身,全部以插件形式挂载在共享的服务上下文上。换一个模型提供方不用改源码,换一个沙箱后端不用重启,换一个 Agent Loop 本身也不用动框架。
这套设计的唯一“真内核”是 Cordis:一个极小的元框架,只做一件事——加载插件、卸载插件、解析它们之间的依赖。Cordis 最初由开发者 Shigma 为一个聊天机器人框架 Koishi 编写,2022 年独立开源,后来被 DeepSeek 连同作者一起并入仓库。Cordis 对模型、工具、甚至“什么是一个 Agent”没有任何意见;所有你以为属于核心行为的东西,都是坐在它上面的插件。
三个机制支撑起这套插件树。Context 是所有插件共享的服务总线,每个能力有一个固定的服务键,比如 ctx.tools、ctx.llm、ctx.sessions;别的插件通过服务键取用能力,不关心这个能力具体是谁写的、跑在本地还是远程。某个能力可以随时换成另一个实现,取用它的插件不用改一行代码。Service 是插件向外暴露能力的出口,inject 声明依赖,Cordis 据此自动调度加载与卸载顺序。更关键的是可逆副作用:插件注册的监听器、服务、命令在卸载时由框架自动回滚,因此热替换是安全的——换模型适配器、换沙箱、换 Loop,都不需要重启进程,系统会回到添加那个能力之前的状态。
组装机制分三层。Profile 是一份存在本地的组装方案,列出要叠放哪些组合包,也存用户自己的配置补丁。Bundle 是组合包,把一组插件和它们的配置打包分发。Patch 按名字定位到某个具体插件,替换它的配置。三层按顺序叠加,每一层都能覆盖上一层的结果。底层还有 dsh-base,每个 Profile 必装,提供模型适配器、工具、持久化、安全策略等基础能力。顶层是四种运行模式——Standard、Code、Minimal、Creator——同一套插件的不同组装,切换模式就是换一套插件组合,不改任何代码。
Loop 的可替换性在 DSH 中是字面意义上的。@deepseek-ai/dsh-agent-loop 是唯一的具体循环插件,且自身可替换;外部不得依赖它。Loop 甚至可以被配置为按 Session 选择不同的实现——内置引擎、Claude Code、Codex、Pi、Kimi 等,理论上都可以作为 Loop 插件接入。
代价同样清晰,而且不在宣传语里。 控制权下放意味着契约稳定性责任转移给了插件作者。DSH 官方明确处于 developer preview 阶段,会存在破坏兼容性的改动。rc.8 不兼容地改了 SQLite 存储格式,alpha.3 移除了可选的 SQLite 会话后端,alpha.4 又改写了读会话事件的方式。同一个核心号的预发布版本之间也可能弹出“含破坏性更改”的警告。插件清单的契约也是一个实际门槛:一个插件必须携带 dsh.bundle manifest,否则 dsh plugin add 会静默地把它当作普通依赖安装,永远不会激活。社区插件审计中,101 个插件里只有 11 个能从 npm 干净安装并开箱即用,81 个根本没有发布到 npm,而是以 GitHub 仓库的形式存在。这不是“生态繁荣”的反面,而是一个早期分发渠道的真实粗糙度。
DSH 适合谁? 需要深度定制运行时语义、愿意承担插件兼容性成本、并且有能力自己维护插件契约的团队。它不适合追求“开箱即用强安全”的场景,也不适合希望插件 API 长期稳定的生产级产品。
3.2 Codex:把控制权收归强类型内核
Codex 走的是相反的极端。它的核心是一个纯 Rust 构建的 workspace(codex-rs,137 个 crate),构成一个高度模块化的单体。core/ 目录下有 582 个 .rs 文件,承担状态机流转、系统调用拦截、Token 预算控制与高并发 I/O 调度。
Codex 对状态的粒度是六种 Harness 中最严谨的。它至少区分 Thread、Session、Turn、Step 和 Tool Call 五个生命周期层级:模型供应商与认证适合 Session 级别,模型参数和权限覆盖属于 Turn 级别,工具快照属于 Step 级别,粘性路由只属于单个 Turn 的 ModelClientSession。判断一个字段放在哪里,设计者给出的四个问题是:它何时创建、何时失效、是否允许恢复、能否被并发观察。很多隐蔽 Bug 都来自状态放错层级——跨 Turn 复用路由 token 导致请求漂移、恢复后换掉基础指令导致行为不一致、工具执行中读取到刷新后的 Registry 导致调用失败。
安全在这里是内核级的硬约束。Linux 用 bubblewrap 与 Landlock,macOS 用 Seatbelt,Windows 用受限令牌,独立的 execpolicy 模块对文件访问和网络外联做细粒度白名单与人工审批。所有动作先变成 Tool Call,再经过 Registry、Router、审批、沙箱和 Runtime 五层校验。Codex 的设计原则第一条写得毫不含糊:模型是决策者,不是系统所有者;优秀 Harness 的核心不是让模型“什么都能做”,而是让它只能通过可观察、可拒绝的通道做事。
命令与事件的分离是 Codex 架构的另一个支柱。外部通过 Submission 告诉 Session“希望做什么”,Session 通过 Event 告诉外部“已经发生什么”;后台循环拥有状态修改权,UI 只消费事件,不直接操作内部状态。这种设计让中断、审批、追加输入和配置变化可以有序进入系统,也让 CLI、IDE、App Server 与测试共享同一运行核心。事实与工作集也被刻意分离:Rollout 保存可重建语义的事实流,Prompt 只是模型当前需要的受控快照;上下文过长时可以压缩 Prompt,但不能伪造或抹去真实执行历史。
代价是扩展深度的限制。 外部扩展——MCP、Skills、规则文件、app-server 协议——只能在 Loop 外面挂载,无法介入或替换核心循环。对于需要把评分模拟、考试矩阵、自定义验证阶段等非标准服务提升为一等公民的场景,集成成本很高:你可以在外面包一层,但拿不到内核级的调度与生命周期语义。
Codex 适合在高风险、强审计环境中可靠执行长任务的生产场景。扩展面窄是代价,换来的内核级确定性是它的价值所在。
3.3 Claude Code:产品级控制力
Claude Code 是一个产品级的 Agent 运行时。基于全部 512,664 行 TypeScript 源码的逆向分析显示,它约 1900 个源文件,核心 Loop 是固定的产品逻辑,但周边扩展面极其丰富:40 多个自包含工具、13 个生命周期事件 Hook、70 多个 React Hooks、多智能体协作,以及 6 种权限模式配合分类器、沙箱与路径验证。
它的问题不在于扩展太少,而在于扩展的深度被产品边界限定。Hooks 是在固定生命周期节点上“插入代码”,而不是“替换生命周期语义”。13 个事件 Hook 覆盖的是工具调用前后、会话生命周期等既定节点,但核心 Loop 本身不可拦截、不可替换。对于一个想要把“评分模拟”作为与工具调用平等的一等阶段塞进循环内部的系统来说,Claude Code 提供不了那个插槽。
Claude Code 的另一个值得注意的架构事实是它的数据管道。逆向分析发现了 Grove 系统——一个隐藏的训练数据基础设施:UI 上写着“train and improve”,开启后数据保留从 30 天延长到 5 年;796 个 telemetry 事件通过双路管道流入 BigQuery,SWE-bench 嵌入每个 telemetry 事件,开发者注释里直接写着 “training data”。这不是架构缺陷,但它揭示了一个事实:产品级 Harness 的“自重”不仅体现在运行时开销上,也体现在它与模型训练闭环的深度耦合上。
Claude Code 适合直接在成熟产品上做周边增强的团队。不要在它的核心 Loop 上做文章,在既定 Hook 面上做深度增强是更务实的选择。
3.4 OpenCode:开放宿主的中间道路
OpenCode 的扩展模型与 Claude Code 有本质区别。它没有基于配置文件的 shell Hook 系统,Hooks 是 TypeScript/JavaScript 插件,订阅生命周期事件,并且能够拦截或阻止工具调用。插件是一个 TS 模块,接收 context 对象并返回 hooks 对象;tool.execute.before 和 tool.execute.after 让插件在工具调用前后介入,experimental.session.compacting 等深度会话钩子则提供了比 Claude Code 更细的生命周期介入。
OpenCode 的 Hook 覆盖 25 个以上生命周期事件,按领域分组:工具、会话、消息、文件、权限。插件可以运行在 OpenCode 进程内,拥有完整的 SDK 访问权限;加载方式包括项目级 .opencode/plugins/、全局 ~/.config/opencode/plugins/,以及 opencode.json 中声明的 npm 包。
它的可插性接近微内核路线——类型安全的模块、进程内执行、能够拦截工具调用——但契约稳定性介于 DSH 和 Codex 之间。历史上插件接口曾发生过重大不兼容变更,这对需要长期稳定的生产级插件套装构成显著风险。OpenCode 适合愿意接受开源生态迭代节奏、需要比 Claude Code 更深生命周期介入的团队,但不适合对插件 API 稳定性有严格要求的场景。
一个值得记录的细节是:截至 v1.14.x,OpenCode 的工具 Hook 不能拦截 MCP 调用。这意味着如果你的安全边界依赖“拦截所有工具调用”,MCP 路径是一个缺口。这不是致命缺陷,但它说明“可拦截”和“可拦截一切”之间还有距离。
3.5 Pi:把复杂性交给调用者
Pi 自称 “minimal agent harness”——极简的 Agent Harness。它的核心哲学是内核最小化 + 扩展可编程化:不内建权限系统,不内建成本追踪,把安全与成本管理的责任交还给使用者。
真正的扩展机制是 pi-fabric。Fabric 给 Pi 一个可编程工具 fabric_exec,让模型在隔离的 QuickJS 或 Python 内核中编写程序来组合工具、MCP 服务器、Agent、工作流、递归,而不是逐次调用工具。默认内核运行受检查的 TypeScript,可以选择沙箱化的 Python(Monty),CPython 作为显式的原生逃生门。分支、循环、扇出、数据流全部在 TypeScript 或 Python 程序中表达;guardrails——审批、隔离、超时、并发、递归深度、共享成本预算——作为可编程运行时的可选层。
这带来了极低的固定开销和极高的分解自由度。一个 Fabric 驱动的 Agent 在 22.4 小时的单次会话中拿下全部 25 个环境,人类时间 4 分钟,模型花费 1,349 美元。这个数字同时说明了两件事:可编程运行时的上限很高,以及“极简”不等于“便宜”——成本取决于任务规模,而不是框架自重。
但 Pi 把大量工程责任转移给了调用者。没有内建沙箱和权限系统,意味着执行不可信代码需要自行构建安全边界。它把“Harness 的问题”变成了“程序设计的问题”——灵活,但不省心。Pi 适合高度技术化、愿意自行构建安全与成本管控、追求最低运行时固定开销的团队,不适合需要开箱即用安全边界的产品化场景。
3.6 Hermes:把自演化做成第一性能力
Hermes 的核心差异化不在扩展机制,而在自演化闭环。v0.12.0 “The Curator Release” 引入了 1,096 个 commit、550 个合并 PR、217,776 行新增。后台 Curator 在独立 cron ticker 上每 7 天自动运行,对技能库进行评分、合并、淘汰;归档分类由模型和启发式联合判定,区分 consolidated(被另一个技能吞并)和 pruned(独立淘汰)。Self-improvement loop 升级为基于 rubric 的分级,带有 active-update bias,偏向更新刚加载过的技能;fork 实际继承父 Agent 的 provider/model/credentials,但工具集被限定为 memory + skills,不能用 shell 或 web。
Curator 的代码规模说明了它的重量:agent/curator.py 1,781 行,agent/curator_backup.py 693 行,hermes_cli/curator.py 598 行。它解决的痛点是真实的——自改进 Agent 的技能库膨胀和上下文腐化(context rot)——但解法本身也构成了系统的固定开销。
Hermes 适合追求长期陪伴式、可成长 Agent 的场景。它不适合高频、轻量、预算敏感的短任务,也不适合需要快速冷启动和高并发调用的场景。自演化能力的价值需要时间兑现,而不是一次会话就能体现。
四、横向对比:五条轴线上的定位图谱
4.1 Loop 控制权光谱
从“可替换”到“固定内核”:
DSH(Loop 可插件替换,可按 Session 切换引擎)→ Pi(极简扁平循环,通过可编程运行时重组工具调用)→ OpenCode(可拦截工具与会话层,但未必能替换整个 Loop 状态机)→ Claude Code(固定产品 Loop + 13 个生命周期 Hook)→ Codex(强类型固定状态机,Session/Turn/Step 三级模型)→ Hermes(固定 TAOR 循环 + 自演化状态机)。
4.2 扩展深度光谱
从“接管运行时”到“外围协议”:
DSH(服务注入 + Loop 可替换 + 可逆副作用)→ Pi(编程式运行时 fabric_exec,模型写程序组合一切)→ OpenCode(TypeScript 插件拦截工具调用,25+ 事件)→ Claude Code(生命周期 Hook + Skill + 多智能体,但核心 Loop 不可介入)→ Codex(MCP / 规则文件 / app-server 协议,Loop 外面)→ Hermes(工具自注册 + 技能沉淀 + 后台 Curator)。
4.3 安全边界光谱
从“系统级硬隔离”到“无内建边界”:
Codex(bwrap + Landlock / Seatbelt / 受限令牌,五层工具校验)→ Claude Code(6 种权限模式 + 分类器 + 沙箱 + 路径验证)→ DSH(沙箱可插件化,安全取决于插件质量)→ OpenCode(依赖外部或插件,MCP 调用当前不可拦截)→ Hermes(依赖外部)→ Pi(无内建沙箱与权限系统,责任交还调用者)。
4.4 自重与成本光谱
从“最轻”到“最重”:
Pi(内核极简,无内建权限/成本追踪,固定开销最低)→ DSH(中等,Cordis 内核极小,但插件组合的灵活性意味着配置复杂度由使用者承担)→ OpenCode(中等,TypeScript 插件进程内运行)→ Codex(较重,137 个 crate 的单二进制,但执行确定性与内存安全高)→ Claude Code(重,512K 行 TypeScript,功能完备,与训练闭环耦合)→ Hermes(最重,自演化闭环、Curator 后台进程、较长的系统提示)。
4.5 一个关键洞察:Loop 控制权与安全强度往往反向
越允许替换核心 Loop 的 Harness,越难在内核层面提供强安全约束。DSH 和 Pi 把控制权下放,换来了极致的可插性,但安全边界要么依赖插件质量,要么完全交给调用者。Codex 和 Claude Code 把 Loop 收紧,换来了系统级的执行确定性与硬隔离,但扩展只能在既定边界内进行。
这不是缺陷,而是工程目标的映射。安全边界是需要内核级约束的东西:bubblewrap、Seatbelt、受限令牌这些东西之所以可靠,正是因为模型无法通过插件绕过它们。在一个“一切皆可替换”的系统里,如果沙箱本身也是一个可替换插件,那么“替换沙箱插件”就变成了一个合法的操作——而这恰恰是安全模型最不希望发生的事情。
可插性与安全性在架构上往往不可兼得。 一个系统可以在插件质量足够高、契约足够严的情况下同时做到两者,但那需要额外的工程投入和纪律,而不是免费的架构红利。
五、选型启示:不存在“最好的 Harness”,只存在“与目标自洽的 Harness”
按工程目标匹配:
需要深度定制运行时语义(替换 Loop、注入一等公民服务) → DSH 或 Pi。DSH 的运行时保障更完整(可逆副作用、依赖解析、HMR),Pi 的固定开销更低但安全责任更大。如果你需要的是一个“可以在它上面重建 Harness 本身”的底座,DSH 是更完整的选择;如果你需要的是一个“把 Harness 问题变成程序设计问题”的最小内核,Pi 是更锋利的工具。
需要在不可信环境中安全执行、强审计长任务 → Codex。扩展面窄是代价,换来的内核级确定性是它的价值所在。五层工具校验和平台原生沙箱不是装饰性的功能,而是在模型可能出错的前提下仍然守住系统边界的硬约束。
需要成熟产品能力 + 周边增强 → Claude Code。不要在它的核心 Loop 上做文章,在既定 Hook 面上做深度增强。它的产品完备性和多智能体协作能力是现成的,但它的 Loop 和训练管道不是你应该触碰的部分。
需要比 Claude Code 更深的生命周期介入、且能承担插件 API 迭代风险 → OpenCode。它的 TypeScript 插件模型提供了真实的过程内拦截能力,但你要接受开源生态的迭代节奏和 MCP 拦截缺口。
需要长期可成长、跨会话学习的 Agent → Hermes。接受它的自重,用时间换能力沉淀。Curator 解决的是一个真实的问题,但它的成本结构决定了它不适合短任务和高并发场景。
一个值得单独指出的反模式:用“可插性”去解决“安全性”问题。 最常见的选型错误,是选择一个可插性极强的 Harness,然后寄希望于“通过插件把安全性做上去”。但可插性与安全性在架构上往往反向:如果安全是硬需求,选一个在内核层面就提供硬隔离的 Harness,比在一个可替换一切的系统上自行构建安全层要可靠得多。插件安全层的问题不在于它做不好,而在于它可以被绕过——而内核级的硬约束不能被绕过,只要你不替换内核本身。
六、结语:拆解的终点是“责任分配”
六种 Harness 拆完,一个更根本的图景浮现出来:架构选择的本质,是决定哪些责任由框架承担、哪些责任转移给插件作者或调用者。
DSH 把责任下放给插件生态,换来了组合自由,代价是契约稳定性与安全质量的责任转移。Codex 把责任收归内核,换来了执行确定性,代价是扩展深度的限制。Pi 把几乎所有非核心责任交还调用者,换来了极致的轻量。Hermes 把“成长”本身做成了框架责任,代价是系统自重。Claude Code 与 OpenCode 则在这条谱系上分别偏向产品控制力与开源可插性。
没有一种分配是“正确”的。正确的分配,是与你愿意承担的责任相匹配的那一种。
Harness 系统级拆解最终要回答的,不是“哪个 Harness 最强”,而是“你准备让谁来为运行时的哪些行为负责”——以及,你是否清楚地知道,当你选择了一个把某项责任“下放”的框架时,你实际上是把那份责任接了过来,而不是把它消失了。
建议引用格式
良之(2026年10月30日).《Harness 系统级拆解:从“控制权分配”看 Agent 运行时的六种形态》. 良之笔记 — Liang.World. 检索于 https://liang.world/post/agent-harness-six-runtime-forms