架构横评与可组合性的工程哲学:dsh vs Claude Code vs Cursor vs Codex
这四个工具不在同一条赛道上:Claude Code、Cursor、Codex 是拿来就用的产品,
dsh是一个往上搭产品的运行时。横评六个架构维度之后,最值得记住的结论有两个。其一,开源有两种含义:Codex CLI 的 Apache-2.0 开源意味着"看得到代码",dsh的全插件化意味着"内核可替换",前者并不蕴含后者。其二,可组合性是一笔前期交税、后期收息的账,选哪边取决于你是要用工具,还是要做基础设施。 事实和判断分开写:工具形态、许可证、扩展机制来自公开资料和官方文档,"适合谁"的结论是我的归纳。
先说比什么
不比功能清单,不比模型能力,不比 benchmark 分数。这三样随版本剧烈变化,依赖太多外部因素,写下来三个月就过期。
比六个架构维度:架构定位(这个工具本质上是什么)、内核归属(agent 的核心循环谁说了算)、模型锁定(锁不锁模型)、扩展方式(怎么扩展能力)、可组合性(能力能不能组合和替换)、抽象代价(复杂度和开箱即用怎么换)。这些维度比功能清单有预测力,因为功能是版本函数,架构是天花板函数:一个工具能长出什么功能,早被它的架构位置框住了。
benchmark 分数尤其容易误导架构读者。SWE-bench 上的名次量的是模型加 harness 的合成表现,你分不清分数里多少来自模型能力、多少来自 harness 的工具设计,而你要选的恰恰是 harness。把模型当控制变量、把架构当观察对象,是横评能做的事;反过来把架构当背景板去比分数,什么也选不出来。
还有一条写作纪律:事实和判断分开。说 Claude Code 是闭源 CLI,这是事实;说它适合谁,这是判断,判断会标明是我的归纳。
四个工具分别是什么
DeepSeek Harness(dsh) 是 DeepSeek AI 开源的 agent harness,MIT 许可,TypeScript 加 pnpm monorepo 实现。仓库的 packages/ 目录按能力域切分:core、llm、sandbox、session、mcp、acp、e2b、subagent,每个域下面再挂具体的包。它处在 developer preview 阶段,官方 README 用大写字体提醒会有破坏兼容性的改动。定位上没有特权核心,一切皆插件,连 agent loop 本身都是插件树上的一个可替换节点。
Claude Code 是 Anthropic 的 agent 客户端,CLI 形态,闭源。agent loop、工具签名、context 压缩策略由 Anthropic 写死,用户通过 CLAUDE.md 项目指令、skills 技能目录、hooks 生命周期钩子在外层定制。
Cursor 是 AI 代码编辑器,基于 VSCode fork,闭源。agent 能力嵌在编辑器里,Tab 补全、Chat、Agent 多种交互形态,支持 Claude、GPT、Gemini 等多家模型,但 agent 的行为逻辑由 Cursor 控制。
OpenAI Codex 是 OpenAI 的 coding agent,入口覆盖 CLI、云端环境、GitHub 集成。这里要纠正一个流传很广的误读,本篇早期版本也这么写过:Codex CLI 并不是闭源的。它的仓库在 GitHub 上以 Apache-2.0 发布,源码可读、可提 PR、可自己编译。闭源的是云端服务和模型本身。所以"Claude Code 和 Codex 封闭、dsh 开源"这个二分法不成立,成立的是更细的辨析,放在下一节。
一个形态上的关键差异:dsh 是运行时,你在它上面搭自己的东西,它的 Web UI 只是众多 profile 之一;另外三个是产品,拿来就用,CLI 或 IDE 就是它们的全部。这个差异不是优劣,是服务对象不同:产品服务终端用户,运行时服务造产品的人。顺着这个视角看后面六个维度会更顺:每一条具体差异,背后都是"为用户优化"和"为构造者优化"这两个目标在机制上的投影。
内核归属:开源不等于内核可换
讲这个维度前先把两个术语落到白话。harness 圈子里把 agent 的支撑结构分成两层:inner harness 是厂商内置、你改不了的部分,典型如 agent loop(决定下一步做什么的循环)、工具执行管线、context 压缩策略;outer harness 是你和团队自建的部分,项目指令文件、技能目录、钩子脚本、CI 集成。四个工具的差别,本质是这层分界线画在哪。
| 工具 | inner harness | outer harness |
|---|---|---|
dsh |
不存在(全是插件) | 整个插件树 |
| Claude Code | Anthropic 写死的 loop、工具、压缩 | CLAUDE.md、skills、hooks |
| Cursor | Cursor 控制的 agent 行为与补全逻辑 | 项目规则、settings |
| Codex | OpenAI 控制的 loop 演进 | AGENTS.md、Rules、Hooks、Skills |
dsh 的官方架构文档有一句原话:"There is no privileged core to patch",扩展 dsh 的方式是在别的插件旁边再挂一个插件。它的 core/agent 提供 Agent 接口和注册表,core/agent-loop 是实现这个接口的默认驱动,注意"默认"这个词:驱动是插件树上一行可以被 patch 替换的配置,不是焊死的地基。
现在回到开源的两种含义,这是本篇最重要的辨析。
第一种是可读的开源,Codex CLI 属于这类。Apache-2.0 给你读源码、审计安全性、提交贡献、自行编译的权利。但产品路线、默认行为、架构演进的方向盘在 OpenAI 手里;你 fork 一份改出自己的分支,就要永远追着上游跑。inner harness 对你是透明的,但不服从你:你能看,不能换。
第二种是可替换的开源,dsh 属于这类。开源在这里不是重点,重点是没有任何一个部件拥有特权地位。想换掉 context 压缩,写一个插件挂上去,原插件卸载;想改 agent loop 的重试节奏,patch 掉那一行配置。可读性和可替换性是两个正交的属性,四种组合在市场上都存在:闭源不可读且不可换(Claude Code、Cursor)、可读但不可换(Codex CLI)、理论上可换但闭源(不存在,闭源堵死了替换的工程路径)、可读且可换(dsh)。
这条辨析直接改变选型决策。你的需求是理解工具行为、做安全审计,可读就够,Codex CLI 的仓库满足你,还能当教材读。你的需求是换掉压缩策略、换沙箱后端、重排 loop 的行为,可读帮不上忙,你要的是替换权,那是 dsh 的赛道。拿着审计需求去要求可替换性,或者拿着替换需求去满足于可读,都是维度错配。
两个中间地带也存在于这条光谱上。Claude Code 完全闭源,但它的 outer 层做得厚:CLAUDE.md、hooks、skills 三件套能覆盖相当多定制场景,这是刻意的产品化投入。Cursor 闭源且 outer 层最薄,定制面基本由编辑器暴露的配置项决定。outer 层的厚度是闭源产品对"改不了内核"的部分补偿,厚度越大,越少人需要碰内核。
升级的不对称也藏在这条分界线里。闭源产品发新版,agent loop 可能整个变了,你的 hooks 和项目指令要跟着适配,不适配就留在旧版本上。dsh 的等价变更是插件树里的一次替换,你可以选择不跟:把那一行配置钉在当前实现上,其余部分照常升级。可组合性在这里兑现成一种很实际的权力,对变更节奏的否决权。代价是行使否决权的人要自己承担分叉的维护成本,永远钉住等于永远追不上主干,这笔账没有免费的一边。
模型锁定:产品意图,不是技术不可能
dsh 的模型接入是一个叫 ctx.llm 的接缝。官方实现了 llm-deepseek、llm-pi-ai、llm-replay(测试用回放),因为是接缝,任何 OpenAI 兼容端点都能写个适配器挂上,或者直接在配置里声明一条路由。换 provider 是换一条配置行或一个插件的事。
Claude Code 绑定 Claude 家族。企业部署可以走 Amazon Bedrock 或 Google Vertex AI,路由到的仍是 Claude 模型;社区还有 Bifrost 一类的网关,用 OpenAI 兼容代理把 Claude Code 接到第三方端点。但这些是外挂技巧,不在产品承诺之内,版本升级随时可能破坏。Codex 绑定 OpenAI 模型,云端和 CLI 一致。Cursor 支持多家模型,在它集成的清单里切换,清单外的加不进来。它在这条谱系的中间位置:多模型是产品策略换来的弹性,但弹性的边界由集成清单划定,你获得了选择权的一部分,选择权的主人仍然是产品方。
判断这件事时容易踩一个坑:把模型锁定当成技术缺陷。它其实是商业设计,而且有正当理由。厂商客户端的存在意义之一,是把自家模型的特性(思考预算的控制、缓存命中的口径、工具调用的方言)压进 inner harness 的调优里,Claude Code 对 Claude、Codex 对 OpenAI 模型的深度适配,正是闭源产品体验流畅的来源。dsh 放弃这份单模型深度调优,换来对任何 provider 的开放性。两边的账都算得过来,看你站在消费侧还是构造侧。
开放接缝的另一面也该写出来:接缝把自由给了你,也把义务给了适配器作者。dsh 的适配器契约要求守三组承诺:流的形状(用量先于终态、工具参数全程是原始字符串)、token 的三桶口径(缓存部分要从总数里减出来,否则压缩时机的压力曲线是斜的)、错误的稳定 code(重试和压缩按 code 路由,不读报错原文)。闭源产品里这些义务由厂商内部消化、内部测试兜底;开放生态里它们散布在每个适配器作者手里,一个第三方适配器漏守一条,故障就藏进计费漂移或压缩失效这种难复现的地方。开放不是免费的,它把质量责任从单一厂商重新分配给接缝两侧的所有参与者,选开放生态时要先问自己愿不愿意接住这份责任。
扩展方式:改行为参数,还是改能力结构
这是四个工具差异最实质的维度。Claude Code、Cursor、Codex 的扩展是配置级和脚本级:项目指令、规则文件、钩子脚本。它们改变的是 agent 的行为参数,用什么提示、遵守什么规范、在哪个生命周期节点跑哪段脚本。dsh 的扩展是插件级:写一个 Cordis 插件,实现 apply(ctx, config),用 ctx.tools.register() 注册工具、ctx.on() 监听事件,卸载时所有注册按序撤销。插件改变的是能力结构本身:新增一个 service、替换一个子系统、改变事件流的形状。
两级扩展对应两级需求。想让 agent 知道项目的编码规范、在提交前跑个检查、给某个框架加专属提示,配置级足够,这也是市场上大多数定制的真实形态,三个产品因此存在。想换一个文件系统实现、换沙箱后端、给遥测加自定义 sink、重定义审批链路,配置级无能为力,因为你要动的东西在产品里根本没有暴露成配置项,必须插件级。这是基础设施形态的需求,dsh 因此存在。
一个实用的自问:把你的定制需求写下来,如果每一项都能表述成"告诉 agent 某件事"或"在某时刻触发某脚本",用产品;只要有一项表述成"替换某子系统",你就需要运行时。
拿同一个需求在两边各走一遍,分界线会更清楚。需求:让 agent 每次改文件前自动留一份备份。在 Claude Code 里,这是一个 before-hook 脚本的事:钩子在文件编辑前跑一段 shell,把目标文件复制走,配置级扩展的典型解法,够用。在 dsh 里,你同样可以监听工具事件做这件事,也可以走得更深:实现一个在写入路径上先备份的 fs provider,让备份成为文件系统能力本身的默认行为,之后任何走 fs 的工具,包括你将来才挂上去的,自动继承。前一种改的是某次调用的行为,后一种改的是能力的默认值。再往上还有 hook 根本够不着的层:给遥测换自建后端、给审批接内部工单系统,这些在配置级产品里不存在对应的旋钮,不是难度问题,是产品没有把这层开放成配置。
可组合性实测:换一个 provider 移动整个产品
可组合性容易停留在口号层,所以拿远程执行当测试用例,看四个工具各自怎么走。
dsh 的做法源自一条架构约束:filesystem 和 subprocess 两个 provider 共享同一个执行世界。把 ctx.fs 和 ctx.subprocess 指向一个 E2B 远程沙箱,Bash、PTY、LSP 整体跟着迁移过去,不需要为远程执行在任何工具里写 provider 分支。一个 provider 的替换,移动了整个产品的执行位置。
对照另外三条路径。Claude Code 的远程执行靠 Docker 容器或 SSH,这是运行环境的替换,不是 harness 结构的替换,agent 对"我在哪执行"没有结构性认知。Codex 的云端入口提供远程执行,但那是 OpenAI 的基础设施,你选择用或不用,不能换成自己的。Cursor 的执行绑定在它的服务形态里。三条路都通向"能远程",但都不通向"远程能力是我可替换的一等公民"。
替换在配置层怎么落地,也值得走一遍,因为这是"可组合"落到工程上的具体形状。dsh 的组合是分层的:bundle 按列出顺序叠成基线,profile 的 patch 改在其上,home 目录的 patch 再改一层,命令行 --patch 最后覆盖。patch 按行 id 定位,整行替换或插入新行,任何一层插入的东西仍然可以被更上层的 patch 再改。想换掉默认的压缩插件,写一小段 patch 把那一行指向你的实现,其余几十行原样不动。这套机制的隐性代价是:最终生效的配置不再是任何单个文件,它是多层叠加的结果。所以 --dump-config 在 dsh 里不是便利功能,是理解系统的必经入口。
支撑 dsh 这个替换能力的,是两条容易被口号掩盖的底层机制。
第一条,注册是可逆副作用。注册一个工具、一段提示、一个监听器,都是框架追踪的 effect,插件卸载时按序撤销。这不是"开发者记得在 dispose 里手动清理"的编码约定,是运行时保证,每个 registry 都有热重载安全的测试压着。没有这条,"换插件"会留下监听器泄漏和幽灵注册,组合就是空中楼阁。
第二条,拦截靠事件链组合。agent/pre-step、agent/request、llm/stream、tools/* 这些 waterfall 事件的监听器必须调用 next(),多个拦截者因此能串成链。重试策略、限流、审计都是链上的插件,不是写死在 loop 里的 if 分支。想改恢复行为,挂一个监听器,不用改任何核心代码。
还有一条常被忽略的保障值得单独说:dsh 有一条"模型可见即可重建"的运行时不变量,任何进入模型请求的东西,必须能从会话日志重建出来。它和可组合性的关系是制约与兜底:插件可以自由改变模型看到的世界,但每次改变必须留痕、可重放。开放内核之所以不失控,是因为自由度被审计能力包着。没有这条不变量,插件越自由,调试越接近玄学。
这个架构还有一个推论落在 agent 自己身上:既然 agent loop 是插件,运行时修改插件树的通道打开之后,agent 就能编辑自己的组合。仓库的 web-cordis 示例演示了这件事:agent 在对话里挂载、卸载、调整插件,改变自己下一步的能力面。"一切皆插件"走到逻辑尽头,就是自我修改。这在闭源产品里没有对应物:内核不可换的系统,自指只能停留在提示词层。
复杂度的账
dsh 的代价是结构性的,不随版本迭代消失。概念门槛:Cordis 的 fiber 和 effect、profile、bundle、patch、接缝、waterfall,做深度定制前每一个都要过。调试链长:一个行为的根因可能穿过多个插件,MCP 工具不生效,可能是 patch 写错了,可能是 serverName 冲突,也可能是重连预算耗尽,排查要跨层。再加上 developer preview 阶段 API 不稳定,深定制要预留迁移成本。
对这笔代价,dsh 给的补偿工具是 --dump-config:一条命令打印实际加载的完整插件树,任何一行都可以被你自己的 patch 替换。这其实是组合系统的通用排障规律:先看组合状态是否和你以为的一致,再谈行为调试。复杂系统的第一故障源不是某段代码错了,而是你以为装的东西根本没装上。
把一次真实排障走一遍更直观。症状:一个 MCP 工具不生效。在配置级产品里,路径基本是重启、查凭证、翻日志。在 dsh 里,第一站是 dump 配置确认那一行真的被加载了,patch 层叠里它可能被更高层整行替换掉;第二站查 serverName 是否和别的 server 冲突,注册期会拒绝重复的名字;第三站看 MCP 客户端的重连预算是否已经耗尽,预算烧完之后工具静默消失。三站都有确定答案,代价是你要知道有这三站。封闭产品把这几站藏在厂商运维里,开放系统把它们交给用户,这就是"复杂度暴露"的具体形状。
回报侧:任何子系统可替换可组合;可以在它上面做二次开发搭自己的产品;不锁模型,任何 OpenAI 兼容端点;全开源,连 Cordis 框架层都 vendor 进来,可审计到地基。
产品侧的账对称地写。代价:内核不可改,模型锁定(Claude Code、Codex),扩展面受限于产品暴露的配置项。回报:开箱即用,装上就能干活;体验经过大量用户打磨,编辑器集成、错误恢复、交互细节都有专人负责;inner harness 针对特定模型的深度调优,是闭源产品的体验护城河。
两边的账放在一起,形状就出来了:复杂度的税在前期交,学习、调试、迁移都发生在头一个月;可组合的回报在后期收,二次开发、生态复用、深度定制都发生在产品长生命周期里。项目周期短、定制浅,产品赢。做基础设施、周期以年计、定制深到子系统层,运行时赢。这不是口味题,是时间尺度题。
什么时候选什么
以下是判断,标明是我的归纳。
选 dsh,如果:你要搭自己的 agent 产品,需要一个可组合的运行时做底座;你要替换某个子系统(远程沙箱、自定义遥测后端、自定义审批流);你研究 harness 设计对 agent 行为的影响,需要在 loop 层做实验;你用 DeepSeek 模型但不接受被锁死。
选 Claude Code,如果:你用 Claude 模型,想要一个打磨成熟的命令行 agent;你的定制停留在项目指令、技能、钩子层;你不想维护任何基础设施。
选 Cursor,如果:编辑器原生体验对你最重要,Tab 补全和 inline 编辑是日常主力;你的工作流和 IDE 深度绑定;你想在多家模型间切换且不需要自己写 provider。
选 Codex,如果:你在 OpenAI 生态里,要多入口(CLI、云端、GitHub 集成)的 agent;你的团队需要 GitHub Action 做 CI 自动化;另外,想读一个工业级 agent 实现源码的开发者,Apache-2.0 的 codex CLI 仓库本身就是好教材。
再给三种具体处境做快速映射。独立开发者赶一个下周上线的功能:三个产品里挑顺手的就是正确答案,别碰 dsh,学习曲线会吃掉你全部余量。平台团队要给公司搭内部 coding agent,要接自有审批、自有审计、自有模型网关:从 dsh 起步,或者至少按它的接缝划分来设计自己的系统,因为这类需求迟早走到"换 provider"这一步,产品挡在那里。研究者要对比不同 loop 设计对任务表现的影响:dsh 把 loop 变成了可替换的变量,这是实验台属性,产品给不了。
趋势上再给一个归纳:产品侧在成熟度和体验上领先,这个差距还会拉大,因为闭源产品的迭代速度由专职团队保证;架构侧的组合能力是 dsh 独有,产品侧短期内不会跟进,因为可组合性和体验打磨争夺同一份工程预算。变量在开放协议:dsh 同时站在两个开放协议的两端,作为 MCP 客户端能接所有 MCP 兼容的工具服务器,作为 ACP 可驱动方能被任何 ACP 兼容的客户端当引擎用,它的生态因此不限于自家仓库里的插件,还包括两个协议各自生长的工具网络。协议生态起来,开放性的回报按网络效应复利;起不来,可组合性再好也只是单机优势。
收束:可组合性值不值得
回到整个系列的根本问题:agent 时代的基础设施,由少数厂商的黑盒提供,还是由开放可组合的生态提供。
dsh 用两百多个包的工程量回答了"开放内核怎么做"。它把"一切皆插件"从口号落到有运行时保证的事实,靠的是三件事:注册即副作用,卸载按序撤销;依赖即声明,加载顺序由需求推导;替换即迁移,一个 provider 换掉,整个执行世界跟着走。口号和工程的距离,就在这三条保证有没有被测试压着。
从它的实践里挑可迁移的经验,我选这四条,因为它们不依赖你用不用 Cordis。第一,让框架追踪副作用,别靠人记得清理,泄漏的监听器是长生命周期系统最常见的慢性病。第二,能力用接缝组织,不用直接 import,定义、提供者、消费者三角色让替换成为结构允许的操作而不是重构手术。第三,恢复策略用事件链表达,不硬编码在主循环里,可插拔的重试比写死的重试活得久。第四,外部世界不可靠是常态,为每一类不可靠设计具体规则,而不是一层笼统的 try-catch。
还有两条实践不起眼,但都在省钱。文档是构建产物:dsh 的文档从源码生成,一致性靠门禁验证,文档和代码打架就让 CI 失败。这一条把"文档腐烂"从事后偿还的债务变成提交时就被拦截的错误,对包数量以百计的系统尤其关键,人肉 review 撑不住那个量级。测试测真实入口:不只测手工拼装的 context,而是通过真实 Loader 启动完整的插件组合,mock 只 mock 昂贵边界,下游用真实实现。"单测全绿但产品是坏的"这类 bug,出在组合这一步而不是单个插件里,只有走真实入口的测试抓得住。换句话说,插件化系统的工程预算应该向接缝和入口倾斜,而不是向单个部件的覆盖率倾斜。
dsh 不是唯一答案。封闭 harness 在产品成熟度上领先,在开箱即用上更好,大多数场景下它们是正确选择。但 dsh 证明了另一种可能在工程上成立:agent 的核心基础设施可以是开放的、可组合的、可审计的,代价是一笔明码标价的复杂度税。这个可能值不值得追求,取决于你相信 agent 基础设施的未来由谁提供。dsh 押了开放那边,这个系列拆的就是这笔押注的工程含义和它的价格标签。
延伸阅读
- DeepSeek Harness 官方仓库
- 官方架构文档:no privileged core、执行世界、waterfall 的原始表述
- openai/codex 仓库:Apache-2.0 的 Codex CLI 源码
- Cordis 时空可组合性论文
- Harness Engineering 是什么
- Codex 工程化实战系列
- Claude Code 工程化系列
- SWE-agent: Agent-Computer Interfaces
上一篇:Cordis 生态溯源:从 Koishi 到 DeepSeek Harness 的插件框架谱系 下一篇:系列完结
GitHub 原文:48-architecture-comparison-dsh-vs-claude-code-cursor-codex.md
评论
EMPTY