DeepSeek Harness 拆解:从「一切皆插件」看 Agent 运行时的架构思路

GitHub 5 天 4 万星,MIT 开源的 DeepSeek Harness 为什么值得每个搭过 Agent 的人认真看?Cordis 的可逆副作用、插件热插拔、Append-only 会话日志,这些设计解决了什么问题?

从一个问题开始

同一个模型,接入 8 种不同的 Harness,完成 30 项多步骤任务——只有 6 项被全部完成,其余 24 项的表现因 Harness 不同而参差不齐。

这是 2026 年 8 月 Composio 公布的一组对照测试数据。测试的模型是同一个 DeepSeek V4-Flash,变化的只有外面的那一圈”脚手架”。这个结果在 Agent 圈子里引发了广泛讨论,因为它指向一个越来越清晰的共识:

模型决定能力上限,Harness 决定实际下限。

换 Harness 带来的体验差,常常大于换模型本身。

DeepSeek 在这个时间点推出了 DeepSeek Harness(以下简称 dsh),MIT 许可证,5 天 GitHub 4 万星。它的核心主张是”一切皆插件”——连 Agent Loop 本身都是可替换的插件。

这篇文章不做发布通稿的复读机。我把仓库文档、源码、Cordis 设计论文和社区讨论都翻了一遍,想回答的是给 Agent 开发者的三个问题:

  1. “一切皆插件”在工程上到底怎么落地?
  2. 哪些设计值得直接借鉴到自己的 Agent 项目里?
  3. 这套方案的局限在哪里?

一、为什么 Harness 在 2026 年突然值钱了

先给” Harness”下个定义。它不是模型,也不是某个具体 Agent 应用,而是包裹在模型外面的那一整圈运行时:

用户输入
  ↓
主循环怎么转(ReAct / Plan-and-Execute / CodeAct?)
  ↓
工具怎么注册与调度
  ↓
上下文怎么组装与裁剪
  ↓
会话怎么持久化
  ↓
代码在哪个沙箱里跑
  ↓
失败了怎么恢复

过去一年行业被反复上了一课:同样的旗舰模型,接在一个粗糙的循环里表现平平;接在一个精心设计的 Harness 里——上下文管理得当、工具返回处理细腻、失败有重试策略——立刻判若两”模”。

与此同时,Harness 领域的现状用一个词形容是”各自为战”:闭源产品(Claude Code、Codex CLI)的 Harness 与自家模型深度耦合,不开放;开源框架要么是上一代的重量级编排库,要么是每个团队攒的私有轮子。

社区里流传的自嘲很传神:

我用的 Harness 已经多到,需要再来一个 Harness 管理这些 Harness 了。

病根是缺一个开放的、中立的、专门为”被替换”而设计的底座。dsh 报的位置就是这个。


二、Cordis:四年聊天机器人社区磨出来的插件内核

dsh 的地基是 Cordis——这不是 DeepSeek 为了 Agent 专门发明的,而是一个通用插件运行时框架,来自 Koishi(跨平台聊天机器人框架,接 QQ / Discord / Telegram / 微信)。

Cordis 只做三件事:

  1. 依赖注入(Dependency Injection)
  2. 作用域服务(Scoped Services)
  3. 生命周期清理(Lifecycle Cleanup)

把它做成 MIT 开源之后,DeepSeek 把整个 Cordis 源码 vendor 进自己仓库,将自研的每一个包都对它声明 peer dependency——这比”用了一个第三方库”更进一步:整个产品构建在 Cordis 之上。

五个核心概念

Cordis 的概念模型浓缩成五句话:

1. 插件是实现 Service 的对象

// 三种合法插件形态:
// ① 函数插件
function myPlugin(ctx: Context, config: MyConfig) {
  ctx.provide('greet', (name: string) => `Hello, ${name}!`)
}
myPlugin.inject = ['llm']   // 声明依赖:等 llm 服务就绪才启动

// ② 类插件
class MyService extends Service { ... }

// ③ 对象插件
const plugin = {
  name: 'my-plugin',
  inject: ['tools'],
  apply(ctx, config) { ... }
}

2. 上下文是服务的容器

ctx.llm      // LLM 服务
ctx.tools    // 工具流水线
ctx.sessions // 会话管理
ctx.agents   // Agent 协调

调用方通过键查找服务,而不是 import 具体实现——换一个 LLM 实现,所有依赖 ctx.llm 的插件无需改动。这条是依赖倒置的支点。

3. inject 声明依赖,框架自动排序

myPlugin.inject = ['llm', 'tools']
// 框架等所有依赖就绪后才启动插件;依赖消失自动卸载,依赖恢复自动重载

4. 类型化事件用于通信

ctx.on('tool/call', (tool) => { ... })        // 监听(随插件卸载自动取消)
ctx.emit('message/send', msg)                  // 分发事件
ctx.waterfall('internal/config', ...)          // 通知,不等待返回
ctx.parallel('task/run', ...)                  // 并行扇出
ctx.serial('task/query', ...)                  // 按序直到有结果

5. 注册是可逆的副作用

这是 Cordis 最硬核的设计,下一节重点展开。


三、最硬核的设计:可逆副作用与热插拔

问题:插件卸载时,谁来清理它留下的副作用?

几乎所有 Agent 框架都宣称自己是”插件化”的,但有一个被广泛忽视的问题:插件注册了事件监听、定时器、数据库连接,卸载时这些残留要么靠开发者手动清理,要么直接泄漏。

Cordis 给出的答案是一条贯穿全局的纪律:

任何对运行时上下文产生修改的操作,必须同时登记一个对应的”撤销动作”。

ctx.effect():副作用的”可逆注册”

ctx.effect(() => {
  // === 做副作用 ===
  const timer = setInterval(() => {
    console.log('心跳')
  }, 5000)
  const off = ctx.on('tool/call', handler)

  // === 返回撤销函数 ===
  return () => {
    clearInterval(timer)
    off()  // 自动取消事件监听
  }
})

当这个插件被卸载时,Cordis 自动调用返回的撤销函数,按 LIFO(后进先出) 顺序执行——最后注册的先撤销,保证依赖关系不被破坏。

这个机制带来三个直接收益:

收益说明
失败原子性插件初始化中途出错,已经生效的一半副作用会被逆序清理,不留半初始化状态
级联清理子插件被卸载时,整棵子树连带按栈序拆除,无需额外维护依赖清理表
热插拔干净插件可在不重启整个系统的情况下加载和卸载,不留孤儿引用或僵尸监听器

热模块替换(HMR)的实现

Cordis 的 HMR 插件做了三件事:

  1. 监视源文件 → 监听 Node 模块图变化
  2. 卸载旧插件 → 可逆副作用自动回滚
  3. 加载新插件 → 重新执行 apply(ctx)
// Cordis 的 Fiber 状态机
// PENDING → LOADING → ACTIVE → FAILED/DISPOSED → UNLOADING
// 依赖声明让 Fiber 在 PENDING 等所有依赖就绪,然后转为 ACTIVE

这个机制在 Koishi(聊天机器人)场景已经实战了四年。dsh 把它引入 Agent 运行时,逻辑是:聊天机器人的痛点(插件热更、依赖管理、多平台适配)与 Agent Harness 今天要解决的痛点高度重叠,开源世界最好的样子就是这样——把社区验证过的答案捡起来复用。


四、九类插件位:没有特权核心

理解了 Cordis,再看 dsh 的”一切皆插件”就不觉得夸张了。九类插件位全部可替换:

┌─────────────────────────────────────────┐
│               用户界面(UI)              │  ← 插件
├─────────────────────────────────────────┤
│            编排(Orchestration)          │  ← 插件:单 Agent 循环 ↔ 多 Agent 协作可切换
├─────────────────────────────────────────┤
│            主循环(Agent Loop)            │  ← 插件:Standard ReAct ↔ PTC CodeAct ↔ ...
├─────────────────────────────────────────┤
│            会话日志(Session Log)         │  ← 插件:append-only 事件溯源
├─────────────────────────────────────────┤
│            沙箱(Sandbox)                │  ← 插件:隔离执行
├─────────────────────────────────────────┤
│            存储(Storage)                │  ← 插件
├─────────────────────────────────────────┤
│            技能(Skill)                  │  ← 插件
├─────────────────────────────────────────┤
│            工具(Tool)                   │  ← 插件
├─────────────────────────────────────────┤
│            模型适配器(Model Adapter)      │  ← 插件:DeepSeek ↔ Claude ↔ 本地 vLLM
└─────────────────────────────────────────┘
           ↓ Cordis Context(服务键发现 + 可逆副作用)

这里最激进的是:连”决定下一步做什么”的 Agent Loop 本身也是插件。 在绝大多数框架里,主循环是”焊死”的——你用这个框架,就接受了这个循环范式,想换?等于换框架。但 Agent 领域循环范式还在高速演化(ReAct → Plan-and-Execute → CodeAct → Tree Search),把 loop 焊死在框架里等于给框架预定了保质期。

四种官方模式的本质:配置组合,不是代码分支

dsh 自带的四种运行模式,不是四套独立 Agent,而是同一套插件池的配置组合:

模式默认插件集适用场景
Standard完整工具集日常主力
Code(PTC)模型生成代码编排多轮工具调用长链路多步任务(CodeAct 范式)
Minimal仅一个 shell + 一个编辑器模型基准测试(控制 harness 变量)
Creator标准 + 运行时自省 + 内存插件实验二开插件、自定义 preset

Minimal 模式的深意被很多人低估了。 评测模型时,Harness 是一个混淆变量——你测出的到底是模型能力还是脚手架功力?Minimal 模式给出了一个控制变量开关:把 harness 压到最小(一个 shell、一个编辑器),模型的裸能力被剥离出来。固定模型、切换插件组合,测的就是 Harness 本身。把”评测的对照组”做成产品功能,这是第一次在开源 Harness 里看到。

熟悉软件史的读者会想到两个类比:VS Code(极小核心 + 扩展宿主,连”内置功能”都以扩展实现)和微内核操作系统(内核只管最小机制,文件系统、驱动、网络全部跑在核外服务里)。dsh 的野心比 VS Code 还激进一格:VS Code 的编辑循环终究是核心,而 dsh 连循环都放进了插件位。


五、Append-only 会话日志:事件溯源进 Agent 运行时

如果说插件化是 dsh 的骨架,会话子系统就是藏在骨架里的杀手锏——海外工程师社区反应最热烈的其实是这一条。

官方描述:

模型看到的一切,都被记录在一份 append-only(只追加) 的会话日志里——系统提示词、推理过程、工具调用与结果、子代理调度、以及每一次上下文注入。

四种操作全部构建在同一条事件流之上:

Append-only 事件流
    │
    ├── Resume(续跑):从中断处继续
    │
    ├── Fork(分叉):从历史中间长出分支
    │
    ├── Search(检索):按来源过滤,打捞上下文
    │
    └── Replay(重放):重新跑一遍,观察差异

这本质上是事件溯源(Event Sourcing)进了 Agent 运行时。做过后端的读者立刻能对上号:状态不是被覆盖的快照,而是事件的累积;任何历史时刻都可以重建。

这条设计解决 Agent 工程里最疼的三件事:

调试:Agent 出了怪行为,以前对着残缺的日志猜”它到底看到了什么”;现在上下文注入逐条在案,按来源过滤,水落石出。

实验:想验证”换个 system prompt 会不会好”?Fork 一条历史轨迹,改一个变量,新旧两条线对比着看。

合规:金融、医疗、法律场景里,审计轨迹是刚需。append-only 日志天然满足不可篡改的要求。

类比一下:git 之于代码,就是这条 session log 之于 Agent 行为。


六、值得借鉴的三个设计

dsh 对我自己的 Agent 项目最有启发的三点:

1. 先想清楚”插件的卸载语义”

大多数 Agent 项目的工具注册是单向的——加载了就一直存在,没有优雅的卸载路径。一旦需要动态增删工具(场景切换、权限变更),就只能硬重启。

借鉴:给每个工具注册配上 dispose() 方法,框架在卸载时统一调用。用 TypeScript 的 Disposable 接口约束这个契约。

2. 用 append-only log 而非快照覆盖

把会话状态从”最新快照”改成”事件流”,代价不高(加一个 event log 模块),但换来了 fork / resume / replay 三个强大功能。调试长对话时尤其有用——Agent 跑飞了,不用从头重跑,直接从断点 fork 出一条新轨迹接着试。

3. 把”评测控制变量”做进产品里

Minimal 模式这个设计很值得产品化:当你需要证明”换了某层组件确实有效果”,而不是”换了 harness 所以有效果”的时候,一个最小可比的 baseline 是无价之宝。


七、局限与风险

诚实地说,v0.1 阶段的 dsh 有几个需要注意的点:

1. 接口会破坏性变更。 官方用大写字母明示”必然会有破坏性变更”,定位是开发者预览,不是生产主力。建议先 pin 精确版本(如 0.1.0-rc.x),不要承载每日生产工作流。

2. ctx.effect() 只处理 Cordis 追踪的修改。 对于外部世界的不可逆操作(已发送的 HTTP 请求、已写入数据库的数据、已发出的邮件),框架无法自动回滚。插件作者需要自己处理这类边界。

3. 生态还在早期。 官方发行自带 130+ 插件,但相比 Claude Code 等成熟产品,工具生态和最佳实践还在快速积累期。


结语

Harness 的价值不在”又多一个编码 Agent”,而在把 Agent 的执行层做成可拆解、可重组、可审计的开放基座。

Cordis 内核只管插件生命周期与事件,九类能力平面全部可替换。四种 preset 让同一宿主适配从日常编码到插件二开的全谱系场景。Append-only 会话日志让 Agent 行为可追溯、可分叉、可重放——这条设计解决的不只是调试问题,而是整个 Agent 工程实践的可靠性基础。

回到开头那个 6/30 的数字。同一个模型在八个 Harness 里有截然不同的表现——这说明在 Agent 领域,“用什么模型”和”用什么底座”同等重要。模型能力在收敛,Harness 工程在分化,竞争的重心正在漂移。理解 dsh 和 Cordis 的设计思路,不是为了换一个框架,而是为了看清这个领域正在往哪里走。


本文参考资料:DeepSeek Harness GitHub、Cordis 官方 Primer、Cordis 论文:A Programming Paradigm for Spatiotemporal Composability(注:论文链接为示意,请以官方实际地址为准)。