Agentic AIHarness EngineeringDeepSeek

深度剖析 DeepSeek 最新的 Harness DSH:为了自进化这盘醋包了一整盘饺子

Codex 有自己的 harness,Claude Code 有自己的 harness,OpenCode 有自己的 harness,连 Kimi 都有了自己的 harness。DeepSeek 一直保持沉默。8 月 13 日,它终于发布了第一个版本的 harness,叫 DSH(DeepSeek Harness),代码开源在 GitHub 上。如果你去看它配套的论文,标题叫 A Programming Paradigm for Spatiotemporal Composability,满是范畴论符号,∂Γ、twisted composition、monoid homomorphism 一个接一个。刚看到这些的时候,很难不让人产生警惕:这会不会又是研究员在真空里搞出来的球形鸡?理论上堆得很优美,真到了工程里根本没人用?

但 DSH 跟其他 harness 非常不一样,不一样到该把它拆开仔细看看。我选了同样开源的 Codex 做对照。Claude Code 和 Cursor 闭源没法看代码,Codex 的源码在 GitHub 上公开,能真正逐行比较。核心想搞清楚一件事:DSH 和我们手头现有的 agent harness 相比,底层架构的本质差异究竟在哪里?这个差异对谁有用,对谁又是多余的负担?一个初步的线索:在 DSH 里,连 agent loop 本身都是插件,可以在 AI 运行的过程中动态替换。而在其他所有 harness 里,这段逻辑都写死在核心代码里,不论是人类还是 AI 都没办法动态更改。这件事听起来简单,但它的后果会一路延伸到整个架构的每个角落。

声明式 vs 命令式:两种插件模型对比

声明式 vs 命令式:两种插件世界观

先从我们熟悉的 Codex 说起。你在 Codex 里用着 Tavily 搜索,想换成 Brave Search。打开配置文件改两行,保存重启 harness,旧的搜索进程关掉,新的拉起来。三秒钟,用户基本无感。Codex 的插件就是一个磁盘上的文件夹,里面放着声明性资源:Markdown 写的 skill 文件、启动 MCP server 的配置、或者特定事件触发时跑一下的 shell 脚本。插件本身不会在 harness 进程里跑代码,它只是一堆文件和配置,harness 读取它们并按需拉起独立进程。这种插件叫声明式的。

声明式的好处是简单。harness 不需要管插件的生命周期,操作系统帮你管进程的生死。插件之间也不需要互相知道对方的存在,各贡献各的,harness 负责调度。但代价是:插件只能贡献文件和配置,不能在 harness 进程里注册有状态的服务、监听事件、或者被其他插件直接函数调用。换句话说,插件能做的事被限制在”给 harness 提供素材”这个层面,改不了 harness 自身的行为。

DSH 走的是另一种路。它的插件直接在 harness 进程里跑,带着自己的状态,跟 harness 同生共死。一个插件装载时,它会在进程里注册自己能做什么,比如”我提供搜索能力”、“我提供数据库连接”。别的插件需要什么能力,就声明一下”我需要数据库”,框架负责把两边接上。插件之间通过框架这个中间人互相找着用,而不是像声明式那样各管各的。这种插件叫命令式的:它带着状态,跟 harness 跑在同一个进程里,被别的插件直接调用。

命令式立刻带来一个声明式不存在的问题:如果你把一个插件换掉了,正在用它的人怎么办?Codex 不需要操心这个,因为它的插件贡献的是文件和配置,换掉了下次读的时候就是新的。但 DSH 的插件是跑在 harness 进程里的代码,别的插件可能正握着它的引用,旧的一关,引用就悬空了。框架必须有能力把旧对象关掉、清理掉它占着的连接和后台任务、创建出新对象、再把所有引用切过去。DSH 背后有一套叫 Cordis 的运行时框架专门干这件事。它替每个插件记住自己装载时做过哪些改动,卸载时一件件撤掉。它替插件盯着依赖关系:你依赖的服务来了就通知你启动,走了就通知你停下,换了提供者就通知你重新加载。它还在热更新时做事务性保护,新代码加载失败就回滚到旧版本,不会卡在半重载的中间态。

两种模型的代价都很直接。在 Codex 里写一个插件,就是写几个文件放对位置,门槛低到几乎为零。但你要改 harness 本身的行为,比如换一种 context 压缩策略,得 fork 核心代码重新编译。在 DSH 里写一个插件,必须通过框架的接口注册副作用、声明依赖、返回撤销函数,还要理解插件的生命周期状态机。门槛高不少。但你要改 harness 本身的行为,写一个插件就行,不用碰核心代码。

上限相同,下限不同:很难找到 DSH 真正更好的例子

剥开架构层看,如果不考虑后面会详细讨论的 agent loop 问题,两边的上限其实是一模一样的。最终能做到什么程度,完全取决于你自己写的业务逻辑。如果你写的 Codex 插件想要类似 DSH 那样的热替换能力,比如一个常驻进程的 MCP server 想在不重启的情况下换掉内部逻辑,技术上不难实现,加个 reload 的 API 端点就行。但这件事得插件作者自己去做,框架不会替你管。DSH 这边也一样,Cordis 只负责调度,具体业务怎么清理、怎么恢复,还得开发者自己写清楚。上限由代码质量决定,和选择什么框架关系不大。

真正的差距在于下限。DSH 强制你走这条更重的开发路线,但它把基础设施全给你铺好了:撤销条管理、依赖变动通知、事务性回滚,这些边缘坑点 Cordis 全部踩过一遍(光 fiber.ts 一个文件就有 750 行)。Codex 给了你极大的自由,默认不需要你承担这些复杂度,但如果你想在 Codex 插件里实现同等能力,这些活都得自己干,第一次跑大概率会踩到各种边缘情况。

但在日常开发里,我们究竟什么时候非走这条重路线不可?我花了不少时间尝试找出一个场景,试图证明 DSH 的命令式模型比 Codex 的声明式模型有明显优势,但说实话,并没有找到。

剥开复杂的抽象名词,现实中几乎没有 Markdown 配合 CLI 解决不了的问题。搜索服务本来就是单次或短期交互,改配置重启一次 MCP server 耗时不过两三秒;skill 只是纯文本文件,修改后下次读取自动生效,根本不需要框架操心热替换。真正需要运行时替换的组件,必须满足两个条件:它在进程内持有跨 turn 状态,且这种状态没有持久化到 session log 中。

在 agent harness 的世界里,这样的组件少之又少。上下文管理器算是最典型的一个,它内部持有哪些上下文该保留、哪些该压缩的策略状态,这种状态通常不会落入 session log。但这也许就是极少数的例外了。

对于绝大多数个人 harness 用户来说,声明式插件已经能覆盖所有常用功能了。搜索、skill、hook,这些主流需求用文件加配置就能解决,不需要进程内热替换那套机制。DSH 提供的事务性 HMR、撤销条和依赖响应确实都是真材实料,但它们解决的问题,要么 Codex 靠重启就能轻松覆盖,要么罕见到没必要为此背上整套架构的复杂度。

唯一 Codex 追不上的:agent loop 作为插件

不过在翻代码时,能看到一个 Codex 很难通过补充代码追上的地方。这源于最底层的架构选择。

Codex 里的 run_turn() 是一段硬编码的控制流:先做采样前压缩,再构建上下文,接着发起模型请求,处理工具调用,最后进入循环。它暴露的 contributor trait 只能让你在预设的时间点插 hook,比如在发送请求前改改 prompt,或者在工具调用后做点后处理。但你无法在运行时动态改变控制流的骨架。你没办法把单 agent 循环改成多 agent 协作循环,没办法把一次性压缩改成流式按需加载,也没办法把发起请求 → 等待响应 → 执行工具的串行逻辑,改成发起请求 → 流式解析 → 并行执行工具。

DSH 把 agent loop 放在了 packages/core/agent-loop 里,它本身就是一个普通插件。它向外提供 ctx.agentLoop 服务,同时声明自己依赖 ctx.systemPromptctx.tools。如果你想换一套完全不同的 agent loop,比如换成多 agent 协调循环,只要写一个实现了相同 ctx.agentLoop 接口的插件,在配置文件里把它替换上去即可。框架会自动卸载旧插件,把之前注册的事件监听和服务全部干净撤销,等新的依赖就位后平滑启动新循环。

Agent Loop 作为插件:运行中热替换

这就像传统软件交付和生成内核的区别。传统软件公司交付的是成品家具,一把造型固定的椅子,买回家开箱即用,但你改不了它的结构。生成内核交付的是核心部件加组装说明,用户或者 AI 可以根据需求自由组装和改装。Codex 交付的就是成品家具,里面的 agent loop 已经死死写定,你只能在预留的扩展槽里塞东西;DSH 交付的则是生成内核,agent loop 本身就是一个可以整体拧下来的零件。

这就是所谓为了这碟醋,包了一整盘饺子。Cordis 打造的那整套机制,包括副作用跟踪、依赖变动通知、fiber 生命周期、事务性 HMR,本质上全是在支撑 agent loop 可替换这个核心目标。至于其他特性,不过是这个目标衍生出来的副产品。回滚机制用来在卸载 agent loop 时把脚手架拆干净,依赖通知用来在 loop 换版后提醒相关插件重新加载,事务性 HMR 则防止新代码中的 bug 导致整个 harness 当场崩溃。

自进化 agent:基础设施和模型能力都已就位

这套架构带来的真正改变,是给 agent 的自进化铺平了基础设施。agent 在运行过程中如果自己生成了一个新的工具插件,框架可以在不重启进程、不中断当前任务的前提下将其热加载进来。Cordis 能保证新生成的插件在卸载时不留残余(撤销条自动清场),保证新旧插件间的依赖关系自动协调(依赖通知生效),还能保证生成的代码一旦出错就靠事务性 HMR 回滚到上一个稳定状态。

在模型能力这一侧,条件也已经成熟了。让 LLM 生成一个几十行代码、单一功能的工具插件,这早已是日常开发里的常规操作。生成一个几百行代码、能处理 turn 状态、上下文组装、工具调度和错误恢复的完整 agent loop,虽然挑战更大,但也已经踩在了当前顶尖模型的能力边界之内。

要让模型写出可用的 loop,关键是 harness 必须给它提供充足且准确的上下文:Cordis 的接口规范、现有 agent loop 的代码结构,以及插件生命周期的约束条件。DSH 的架构让这些上下文天然可见,因为它的 agent loop 本身就是写在外面、可读可改的 TypeScript 插件,无需去读藏在二进制深处的控制流。Codex 的 agent loop 编译死在 Rust 代码里,哪怕模型进化出了编写全新 loop 的能力,系统里也没有任何一个接口能把它装上去。DSH 恰好补上了这个物理插槽。

回到最开始的那个疑问:DSH 到底是不是研究员们在真空里造出来的球形鸡?从代码实现来看并非如此。Cordis 的源码里能清晰看到对大量真实工程坑点的妥协与处理,比如并发卸载时的竞态条件、依赖链传播的终止边界,以及回滚时 disposer 执行失败的容错逻辑。这些不像是纯论文推演出来的,倒像是在跑着 4000 多个插件的生产环境里硬生生砸出来的。

不过,它的适用场景远比论文里暗示的要窄。论文把它包装成了一套普适的新型编程范式,但实际上,它专门服务于运行时热替换进程内带状态组件这一特定的架构选择。如果不涉及这种运行时热替换,它试图解决的绝大部分难题根本不会出现。

对于绝大多数日常使用 Codex、Claude Code 或 OpenCode 的开发者来说,DSH 并不会让你的日常写码体验变得更好。Codex 那套声明式模型足够简单,开发门槛低,重启一次两三秒完全可以接受。DSH 里的 Cordis 机制对你而言,更多是无谓的复杂度。

但对于那些试图触碰 agent harness 上限的探索者,希望 harness 能够自我演化、动态切换 loop 策略、一边跑一边给自己生成新能力的人,DSH 是目前唯一把底层基础设施搭建完毕的方案。它不会直接让 AI 变得更聪明,也不会让日常 coding 速度变快,它做到的,是让 harness 本身的行为具备了适应与演化的可能。为了自进化这碟醋,DSH 包了一整盘饺子。饺子好不好吃另说,但醋目前确实只有这里有。

鸭哥每日手记

日更的深度AI新闻和分析