跳到主要内容

内容阅读

架构横评与可组合性的工程哲学:dsh vs Claude Code vs Cursor vs Codex

架构横评与可组合性的工程哲学: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/ 目录按能力域切分:corellmsandboxsessionmcpacpe2bsubagent,每个域下面再挂具体的包。它处在 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-deepseekllm-pi-aillm-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.fsctx.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-configdsh 里不是便利功能,是理解系统的必经入口。

支撑 dsh 这个替换能力的,是两条容易被口号掩盖的底层机制。

第一条,注册是可逆副作用。注册一个工具、一段提示、一个监听器,都是框架追踪的 effect,插件卸载时按序撤销。这不是"开发者记得在 dispose 里手动清理"的编码约定,是运行时保证,每个 registry 都有热重载安全的测试压着。没有这条,"换插件"会留下监听器泄漏和幽灵注册,组合就是空中楼阁。

第二条,拦截靠事件链组合。agent/pre-stepagent/requestllm/streamtools/* 这些 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 押了开放那边,这个系列拆的就是这笔押注的工程含义和它的价格标签。

延伸阅读

上一篇:Cordis 生态溯源:从 Koishi 到 DeepSeek Harness 的插件框架谱系 下一篇:系列完结


GitHub 原文:48-architecture-comparison-dsh-vs-claude-code-cursor-codex.md

评论

0
登录后可以参与评论和讨论。

EMPTY

还没有评论

欢迎留下第一条评论,帮助这篇内容更快形成讨论。