Agent Harness 架构到底需要些什么?
1. 模型不是瓶颈,harness 才是
同一个模型放进不同的 harness,成绩能差多少?CORE-Bench 上,Claude Opus 4.5 跑在通用 CORE-Agent 脚手架上是 42%,换进 Claude Code 的 harness 变 78%;把评测 harness 自己的 bug 一起修掉,还能摸到 95%。模型一个参数没动,36 到 53 个百分点的差距全来自壳——模型不是瓶颈,harness 才是。
要开 agent 开发,先回答一个问题:Agent Harness 架构到底需要些什么?
这个问题会一路影响你后面的每个决策。单独看任何一家,你只能看到一种取舍;把 Claude Code、Codex、DeepSeek Harness(后面叫 dsh)三家摆在一起,才能看出公共规律。Claude Code 是应用层治理,Codex 是内核级沙箱,dsh 是一切皆插件——三种哲学,各自押注了 harness 的一个侧面。
2. Claude\CodeX\DSH:三种哲学
Claude Code = 应用层治理(钩子驱动)。 Anthropic 把控制点放在应用层。官方文档列出的生命周期钩子有二十多个(版本不同,数目在 24 到 30 之间浮动),SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、Stop、SessionEnd……覆盖了循环的每一个确定性节点;CLAUDE.md 从企业策略、用户级、项目级、本地到子目录按需加载地分层;权限落在工具级,每个工具调用都能被审批或钩子拦截。它的逻辑是:harness 应当能「在任何时刻插进干预」。
Codex = 内核级沙箱(硬边界优先)。 OpenAI 把安全重心压进内核。Linux 上用 Landlock+seccomp,macOS 用 Seatbelt,Windows 用受限令牌,还单独编了一个 sandbox 二进制来包命令——沙箱策略和主程序分开。它的逻辑是:边界要在内核层关死,模型和用户的手滑都绕不过去。
dsh = 一切皆插件(可重组运行时)。 DeepSeek 站在 Cordis 上,模型、工具、会话、沙箱、Agent Loop、调度、UI 全是插件,没有特权内核。它的逻辑是:harness 不该有不可替换的部分——连循环本身都能换。
一张表把三家摆开:
| Claude Code | Codex | dsh | |
|---|---|---|---|
| 哲学 | 应用层治理(钩子驱动) | 内核级沙箱(硬边界优先) | 一切皆插件(可重组运行时) |
| 开源 | 闭源 | 开源 CLI(Apache 2.0) | 开源(MIT) |
| 端形态 | CLI + 桌面端 + IDE 插件 | CLI + 桌面端 + Cloud + IDE 插件 | CLI(web / headless boot profile) |
| 扩展机制 | 钩子 / skills / subagents / 插件市场 / MCP | skills + AGENTS.md + 并行 subagents + 插件 | 插件(Cordis,16+ seams) |
| 沙箱 | 应用层 OS 沙箱 + 工具级权限审批 | 内核级(Seatbelt / Landlock+seccomp) | 平台链 seam(bwrap / landlock / seatbelt / windows-acl)+ 审批 |
| 模型绑定 | 默认 Claude 系(可配置切换) | 默认 GPT-Codex 系 | 模型无关(适配器即插件) |
两处我按规矩标注一下。Claude Code 桌面端最早是 Chat / Cowork / Code 三个 Tab,2026 年 7 月起 Chat 和 Cowork 合并成单视图,Code 保持独立。dsh 的「40 多家模型厂商」是发布报道口径,我以官方 README 的「模型无关、模型适配器即插件」为准,厂商清单没逐家核实。
三家的标签压成一句话:Claude Code 管「何时干预」,Codex 管「哪里隔绝」,dsh 管「什么可换」。 三个词不打架,它们回答的是 harness 的三个不同问题。
3. Agent Loop:循环怎么组织
循环是 harness 的心脏。模型不主动做事,它只回应请求——请求怎么来、上下文怎么喂、结果怎么回,就是循环的全部工作。
dsh 的循环我拆过,直接给源码级的细节。ReactLoopAgent(packages/core/agent-loop/src/agent.ts:64)把工作切成两个单位:turn(轮次)和 step(步骤)。一个 step 是一次模型请求加上它调用的工具;一个 turn 包含零个或多个 step。状态机三条:turn()(:246)开循环领 step,step()(:332)构建请求、流式收回复、派发工具调用,buildRequest(:407)组装请求时还能被 waterfall 改写——连「模型这次调谁、用什么参数」都是可插拔的。
消息进来走 Inbox(packages/core/agent/src/inbox.ts:25),双队列 next-turn / next-step,claim()(:71)领走整批输入。我压成一句话:循环不直接读「消息数组」,循环读队列。 输入、注入、中断都变成队列操作,状态机只关心「队列里还有没有活」。这一条换来的是分叉、恢复、回放全挂在同一事件流上,循环本身保持得很薄。
Claude Code 的循环我从官方文档看:一次会话组织成 plan → execute → verify 的 agentic loop,循环外面挂着生命周期钩子,可以在每个确定性节点插进干预——正好呼应它「应用层治理」的哲学。2026 上半年它给循环加了一串「无人值守」旋钮:/loop 按时间重跑、/goal 按条件验收、/schedule 定时触发。这些本质上是给同一个循环装不同的停机条件。
Codex 的循环据官方文档是 plan mode / steer mode 两档——一个先规划再动手,一个让你在循环里持续转向。它跟 Claude Code 一样,循环本身是产品内部件,用户只看到暴露出来的模式,不看到循环内部。
4. 工具与流水线:模型怎么接触世界
循环是心脏,工具是手脚。模型只输出 tool-call,真正做事的是工具——所以工具怎么被审批、怎么被执行、结果怎么回写,是 harness 的「税务系统」,每一笔调用都要过一道关。
dsh 在这条接缝上放了一条完整的执行流水线,六个阶段,显式写在代码里。prepareExecution(packages/core/tools/src/index.ts:1463)跑 pre-execute waterfall,然后审批询问、单调守卫,dispatch(:1569)执行(timeout/retry 包在外面),postExecute(:1742)跑 post-execute waterfall,最后 applyFinalContent(:1649)应用内容变换,tools/result 通知最终结果。
两个细节值钱。一是守卫和审批分开——守卫是已注册的所有者策略,不能重新排序;ctx.approval 是一次性的人机询问,缺了回答方一律按 deny,fail-closed。二是 PTC(程序化工具调用)不绕过安全——code preset 里模型生成一段 TypeScript 程序组合多步操作,但它复用同一个 TOOL_RUNTIME_SCHEDULER(packages/core/tools/src/code-mode.ts:481),每个子调用照样走 scheduler.prepare()(:545)。批处理也关进同一条流水线,不是给模型开后门。
Claude Code 的「流水线」是分散的:工具级权限审批(ask / allow / deny),加上 PreToolUse / PostToolUse 钩子做拦截和记录,PermissionRequest 钩子还能自动应答权限询问。它没有一条显式的六阶段流水线,但同样保证了「工具调用前有一道闸、调用后有记录」——这道闸藏在钩子和权限模型里。
Codex 据官方文档把把关拆成两层:approval_policy 三档(read-only / workspace-write / danger-full-access)管「允不允许」,内核沙箱管「做不做得到」。工具调用先过审批,再被沙箱包一层。
对要搭 harness 的人:工具流水线至少要有 pre 和 post 两道口。 pre 管拦截、审批、改写输入;post 管记录、校验、改写结果。dsh 的六阶段是完整版,但最小可用的 harness 有 pre + post 就能活——流水线可以短,不能没有。
5. 安全边界:沙箱与权限
安全是三家里分歧最大的一节,也是哲学最鲜明的一节。
Codex 押内核级。 Linux 上用 Landlock+seccomp——Landlock 是内核 LSM,管文件系统访问;seccomp 过滤系统调用。策略在单独的 sandbox 二进制里,主程序只把策略 JSON 传给它,它应用规则后 exec 目标命令。沙箱策略硬编码 Landlock ABI V5,内核不支持就直接 panic——宁可跑不了也不裸奔。macOS 走 Seatbelt,Windows 走受限令牌。它的哲学:边界关在内核层,谁都没法绕过。
Claude Code 押应用层。 权限落在工具级,每个工具调用可以问、可以配、可以拦;OS 沙箱(macOS Seatbelt 等)做最后物理防线。它的哲学:边界可以「随时插进干预」——PermissionRequest 钩子能在权限弹窗出现前自动应答,PreToolUse 钩子能直接拒绝某个工具。防的是「误放行」,靠的是人可配置。
dsh 把沙箱做成接缝。 SandboxProvider.confine(packages/sandbox/sandbox/src/index.ts:158)是抽象服务:传入 argv 和策略,返回被包过的 argv。真正的执行走平台链——bwrap、landlock、seatbelt、windows-acl,按平台挑一个,全部 fail-closed(源码注释写得明白:「silent unconfined passthrough is forbidden」)。审批是独立三档:read-only、workspace-write、danger-full-access。它的哲学:边界本身可替换。
对要搭 harness 的人:先定你的安全哲学,再动手。 要防「模型绕过你」——选内核级,像 Codex;要防「人误放行」——选应用层加审批,像 Claude Code;要边界可移植、多平台一鱼多吃——把沙箱做成接缝,像 dsh。三条路没有绝对优劣,但不选一条的代价是边界不可审计。
6. 会话与可观测性
dsh 把这件事焊成了运行时不变式。它的会话不是「消息列表」,是一条 append-only 的 SessionEvent 事件流(packages/core/session/src/types.ts:404),每条事件带 seq,seq 就是 log.length(index.ts:604 的 append 里写死)。事件按角色分三类:边界(turn/start 这类标记)、表面(真正进模型历史的 user/assistant/tool 消息)、仅日志(chunk、usage 这类为保真和回放存在的)。设计原则一句话:模型可见即已记录。
每次 llm 请求,invariant 检查请求的消息数组和 deriveMessages()(index.ts:726)从日志算出来的是否一致,不一致直接 fail(packages/core/agent-loop/src/invariant.ts:21-54)。不是约定,是断言。分叉是日志的纯函数(index.ts:1081 按 boundary seq 切子会话),回放、恢复全是同一事件流的不同读法——日志是唯一真相,其它全是投影。
Claude Code 把会话可观测性做成产品功能:checkpoint 存节点、/rewind 回退、/resume 恢复、stream-logs 看流式日志、/export 导出。对用户够用,但它不是架构不变式——没有一条断言保证「模型见到的必须能从历史重建」。
Codex 据官方文档把会话存在 ~/.codex/sessions/,跨端可恢复,/compact 压缩。同样是产品功能,不是架构不变式。
对要搭 harness 的人,这是我最想让你抄的一条:dsh 的「模型可见即已记录」不用抄成断言,但要抄成习惯。 你至少得能回答一个问题:模型上次请求见过什么?答不出,debug 无从谈起,agent 跑错只能重启。会话状态当数据库管,还是当运行时内存管,是自建 harness 的分水岭。
7. 多 Agent 与编排
单 agent 有天花板:上下文会污染,能力会饱和,一个循环干不了所有事。多 Agent 不是锦上添花,是 harness 的必备件。
dsh 把「子 Agent 从哪来」做成一个接缝。SubagentProvider(packages/subagent/subagent/src/types.ts:285)定义了什么是一次委派,注册了五类实现:spawn(新建子 Agent,inheritsParentContext=false,孩子从零开始,:41)、fork(继承父的已完成轮次前缀,从父的上下文续着干,:48)、acp(进程外 ACP 子 Agent,:146)、claude-code、codex。
最后两个最值得说——dsh 能把一轮委派丢给 Claude Code 或 Codex 的进程当后端。 这是官方记录在案的:仓库的 feature note(2026-08-04-claude-code-and-codex-subagent-backends.md)写了两个 provider 分别调 Claude Agent SDK 和 Codex 的 app-server,每次调用起一个全新产品进程,父会话只收最终答案。这句话的意思比表面大:harness 之间不是互斥的,是可以互相嵌套的——你的主 harness 可以把脏活外包给别的 harness。
更「换循环」的是 workflow 引擎。runtime.ts:90 把一段 workflow 脚本用 vm.Script 编译进 worker thread,脚本里能调 agent()、parallel()(:401,各 thunk 并行,非致命错误归 null)、pipeline()(:428,逐项过阶段、无跨阶段屏障)。Ralph 更极端:内置固定脚本 RALPH_SCRIPT(tool-ralph/src/index.ts:90),每轮派一个全新结构化子 Agent 攻一个目标,子 Agent 只能填数据不能改循环,requireFreshProvider(:220)强制子 Agent 必须是干净上下文,standard preset 里配了 maxRounds: 64。
Claude Code 的多 Agent 是原生子代理加 Task 工具:description 是「招工启事」,isolation: worktree 防改动污染,子代理对主会话无记忆。Codex 据官方文档是并行 subagents:multi_agent 打开后有 spawn_agent / wait_agent / close_agent,内置 default / worker / explorer 三种 agent 模板。
对要搭 harness 的人,最关键的一个旋钮是:子 Agent 继承不继承父上下文。 spawn(从零)适合独立任务,fork(继承前缀)适合续着上下文干。这个二元选择决定了你多 Agent 编排的语义——dsh 用一个 inheritsParentContext 布尔值把它显式化了,别让它在你的架构里变成隐含假设。
8. 可扩展性:从哪下手扩展
harness 没有扩展点就是死壳。但「扩展」的深度,三家差着数量级。
Claude Code 的扩展入口五个,我在 003 拆过,按「加载时机 × 强制程度」两轴排开:CLAUDE.md(常驻、建议)、Rules(路径约束、建议)、Skills(按需加载、建议)、Hooks(确定性、强制)、Subagents(隔离上下文)。再加插件市场和 MCP。关键区别一句话:CLAUDE.md 和 Skills 是建议,模型可以忽略;Hooks 是强制,脚本执行不由模型自觉。 不能靠自觉的底线,只能写 hook。
Codex 的扩展点是 skills + AGENTS.md + 并行 subagents + 插件(企业版)+ MCP。它最有价值的一点是 AGENTS.md 的开放标准属性——同一份指令文件能被多个工具读(Codex 能配置读 CLAUDE.md 做 fallback,反过来 Claude Code 也能引用 AGENTS.md)。这对「工具间迁移」是实打实的解绑。
dsh 的扩展深度到顶:一切皆插件。架构文档画了 16 个以上 seam(llm/fs/shell/subprocess/sandbox/approval/codeRuntime/subagents/workflowEngine/lsp/web/compaction……),每个 seam 由 Service Definition / Provider / Consumer 三件套定义,换掉 Provider 就改变整个产品。连 Agent Loop 都能换——这是它和主流工具的核心差异:主流在工具层给你扩展点,它在每一层都给你扩展点。
对要搭 harness 的人:扩展哲学 = 你允许别人从哪下手改。 三个层级你要哪个:固定点插脚本(钩子,最省事)、按需加载流程(skills,最常用)、换任意零件(插件,最自由)。扩展点深度和架构复杂度成正比——dsh 的 16+ seams 背后是 Cordis 的插件生命周期管理,这层复杂度不是白来的。
9. 合起来:Agent Harness 架构到底需要些什么
到这里,三家拆完。harness 架构没有标准答案,但有公共要素——缺一样就是短板。 三家用完全不同的哲学去实现,最后都长出了同一组东西,这组东西就是答案。
八样要素,每样给你「为什么必须有」和「三家怎么权衡」。
① Agent Loop。 为什么必须有:模型不主动,循环是唯一驱动,没有循环就没有 agent。三家权衡:Claude Code 把循环藏在产品后面,只露出 plan / execute / verify;Codex 用 plan / steer 两档模式;dsh 把循环本身做成插件(ReactLoopAgent 可换)。落点:循环至少要有 turn / step 两级边界,才有地方挂超时、压缩、中断。
② 上下文管理。 为什么必须有:模型窗口有限,注入什么、压缩什么、丢弃什么决定成败。三家权衡:Claude Code 用 CLAUDE.md 分层加 /compact /clear /rewind;Codex 用 AGENTS.md 加项目文档;dsh 用 buildRequest waterfall 改写请求加 system-prompt 组装。落点:上下文是你最常调的旋钮,先想清楚注入点在哪。
③ 工具流水线。 为什么必须有:模型只输出 tool-call,真正做事的是工具,没有流水线就是裸奔。三家权衡:dsh 六阶段显式设计;Claude Code 把把关藏进钩子和权限模型;Codex 用审批加沙箱两层。落点:至少要有 pre(拦截 / 审批)和 post(记录 / 校验)两道口。
④ 安全边界。 为什么必须有:模型不可信,边界是最后防线。三家权衡:Codex 内核级(Landlock + seccomp)、Claude Code 应用层(工具权限 + OS 沙箱)、dsh 平台链 seam(bwrap / landlock / seatbelt / windows-acl)。落点:先定边界关在哪一层——内核级防绕过,应用层防误放行。
⑤ 会话 / 可观测性。 为什么必须有:agent 跑错没法 debug 等于没法用。三家权衡:dsh 把会话当数据库(append-only 日志 + 不变式);Claude Code / Codex 当产品功能(checkpoint / resume)。落点:至少做到「模型每次请求都能从历史重建」。
⑥ 多 Agent。 为什么必须有:单 agent 上下文会污染、能力会饱和。三家权衡:Claude Code 原生子代理加 Task 工具;Codex 并行 subagents;dsh SubagentProvider 五实现,甚至能接 Claude Code / Codex 当后端。落点:先想清楚子 Agent 继承不继承父上下文。
⑦ 可扩展性。 为什么必须有:没有扩展点,harness 就是死壳。三家权衡:Claude Code 钩子 / skills / 插件市场;Codex skills / AGENTS.md / 插件;dsh 一切皆插件。落点:扩展哲学 = 允许从哪下手改——固定点插脚本,还是换任意零件。
⑧ 模型适配。 为什么必须有:没有适配层,harness 锁死一家模型,而模型是这行贬值最快的东西。三家权衡:Claude Code 默认 Claude 系、可配置切换;Codex 默认自家、可配置;dsh 模型无关、适配器即插件。落点:模型适配层越薄越稳,别把模型名写死在业务逻辑里。
这八样是我从三家身上抽出来的公共要素。你可能要反驳:多 Agent 是必备的吗?极简的 harness 用不到。 我的回答是——可以暂时没有,但架构上要留口子。dsh 的 SubagentProvider 是个接缝,minimal preset 只是把接缝关上了,不是没有。「缺一样是短板」说的是结构上留不留位置,不是这版功能开不开。
10. 结论:不用从零造,先定两个哲学
拆完三家,给要开 agent 开发的人一个总判断:不用从零造,三家的取舍就是现成教材。 你不需要重新发明 Agent Loop、工具流水线、会话日志——这三家已经用不同的哲学把每个要素都实现了一遍,你要做的是抄,不是造。
但抄之前,先定两个哲学。
第一个是安全哲学:边界关在哪一层。要防模型绕过,学 Codex 内核级;要防人误放行,学 Claude Code 应用层;要多平台一鱼多吃,学 dsh 把沙箱做成接缝。这一条不定,你后面所有设计都是打补丁。
第二个是扩展哲学:允许从哪下手改。只想要固定点插脚本,学 Claude Code 的钩子;想要流程按需加载,学 Codex 的 skills;想要连循环都能换,学 dsh 的插件化。扩展深度和复杂度成正比,别上来就选最深的。
这两条想清楚,再去对照第九节那张清单,一项项查你的 harness 缺什么。
现在留一个开放题给你,评论区可以吵:现在做 agent,到底该自己搭 harness,还是用现成的? 我的判断是:如果目的不是学架构,别从零造——用现成的,把省下的时间投到你的工具流水线和安全边界上;但如果你就是想掌控所有旋钮,dsh 的插件化是最接近「从零」的现成起点。
如果你认同「公共要素」这个框架,点个赞——这套图纸值得被更多要开 agent 开发的人看见。接来下我会安排自己动手实现一个agent harness,帮助你深入去了解agent harness的搭建。
更多推荐




所有评论(0)