跳过主要内容
全部文章
开发

Cordis 详解:DeepSeek-Harness 背后的插件框架

Cordis 是什么、它从何而来,以及它的 Context、Service、Fiber 生命周期这几个基础原语如何撑起 DeepSeek-Harness 一切皆插件的架构。

Cordis 是 DeepSeek-Harness(dsh)赖以构建的插件框架——一套通用的插件式编程范式,源自 cordiverse/Koishi 生态,并不是 DeepSeek 专门为 dsh 从零写的东西。dsh 的每一个特性,从单个工具到整个 Web UI,都是一个针对 Context 注册的 Cordis 插件。理解 Cordis 那几个基础原语——Context、Service、effect,以及 Fiber 生命周期状态机——是把 dsh 剩下的"一切皆插件"设计从玄学变成可读逻辑的关键。

Cordis 从何而来

Cordis 的出现早于 dsh。它是聊天机器人框架 Koishi 的插件内核,被打包成一个独立于具体领域的依赖注入与插件生命周期库,归属 cordiverse 组织。DeepSeek 把它直接采纳为 dsh 的底层框架,而不是自己重新造一套插件系统——这也是为什么 dsh 的插件文档直接引用 Cordis 的概念(ContextServiceinject),而不是把它们包一层 dsh 专属的术语。

核心原语:Context 与三种插件写法

每个 Cordis 插件都是一个针对 Context 对象(ctx)注册能力的模块。dsh 自己的文档给出了三种等价写法:

// 1. 函数式——最常见的写法
import type { Context } from '@deepseek-ai/cordis'

export const name = 'my-plugin'

export function apply(ctx: Context) {
  // 在这里注册能力
}
// 2. 对象式
export default {
  name: 'my-plugin',
  inject: ['tools'],
  apply(ctx: Context) {
    // ...
  },
}
// 3. 类式——当插件本身要向其他插件提供一个 Service 时使用
export default class MyService extends Service {
  static inject = ['tools']
  constructor(ctx: Context) {
    super(ctx, 'myService') // 挂载为 ctx.myService
  }
}

对 Cordis 的加载器来说,这三种写法是等价的;类式写法专门用于插件本身要暴露一个可被其他插件 inject 并调用的可复用服务,而不只是跑一段有副作用的初始化代码。

Service 与依赖注入

Service 是一个插件暴露给其他插件用的能力,挂载在 ctx.<serviceName> 上——ctx.toolsctx.llmctx.subagents 本质上都是 Cordis 服务。消费方插件用 inject: ['tools'] 声明自己需要什么;在所有被注入的服务真正就绪之前,Cordis 不会调用该插件的 apply();如果某个必需服务后续消失了,Cordis 会自动卸载这个消费者插件(服务恢复后再重新加载)。对于"有更好、没有也行"的服务,用 ctx.get('metrics') 做可选查询,而不是强依赖。

但默认情况下,一个具体的服务名在整个插件树里只会解析到一个共享实例——下一节讲的正是需要按分组分别配置时,打破这个默认假设的机制。

Effect:不用手写 dispose 逻辑的清理机制

ctx.effect() 是 Cordis 对"这个注册在插件卸载时需要清理"这个问题给出的答案:

export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(() => console.log('heartbeat'), 5000)
    return () => clearInterval(timer) // 插件卸载时自动调用
  })
}

一切通过 ctx 完成的注册——事件监听、ctx.tools.register() 调用、effect——在插件卸载时都会被自动清理。你不需要手写一个 dispose() 方法;Cordis 会追踪一个插件注册了什么,并在卸载时把它逆向撤销。

Fiber 生命周期

Cordis 把一个插件的生命建模成一个小型状态机(dsh 的框架文档把这个单元称为"Fiber"):

PENDING → LOADING → ACTIVE
                 ↘ FAILED
ACTIVE → UNLOADING → DISPOSED

一个插件停留在 PENDING/LOADING 状态,直到它 inject 声明的所有依赖都满足,然后进入 ACTIVEapply() 才会执行。如果某个依赖后续消失了,插件会从 ACTIVEUNLOADING 转到 DISPOSED,依赖恢复后再重新进入这个循环。这套状态机也正是热模块替换(HMR)能够工作的原因:挂上 @deepseek-ai/cordis-plugin-hmr 后编辑插件源码,会触发"卸载旧实例 → 加载新代码 → 重新执行 apply()",走的正是依赖丢失/恢复会触发的同一条状态转移路径。

isolate:让不同插件分组拥有各自的服务实例

默认情况下,像 ctx.tools 或一个共享的 Bash executor 这样的 Cordis 服务,在整个进程范围内是一个所有消费方插件共用的单一实例。isolate 在你需要的时候打破了这个默认假设:它让你把一个服务限定给某个插件子集,让这个子集拥有自己独立的实例,而不是共用全局默认实例。dsh 的文档给出了一个具体场景——给不同插件分组各自一个 Bash executor 实例,每组配置不同的超时时间,而不是强迫这个 profile 下的所有插件共用同一个超时值。这在实践中很有用:每当两个名义上"用同一个服务"的能力,实际上因为调用它们的是插件树的不同部分而需要不同运行时行为时,就用得上。

能力接缝:Cordis 对"可替换能力"给出的答案

**能力接缝(capability seam)**是 dsh 术语表里对这些基础原语组合出的完整单元的命名:一个 Service Definition(一个 Cordis Service 抽象类,例如 ShellExecutor)加上一个或多个 Service Provider(具体实现)加上一个或多个 Consumer(inject 它的插件)。packages/shell 包是标准范例:dsh-shell 定义这个接缝,dsh-bash-local/dsh-bash-sandbox 是 provider,dsh-tool-bash 是把它暴露成模型可见工具的 consumer。这是 dsh"一切皆插件"这一更大结论背后的最基础建模单位——完整讨论见我们的架构指南

关于 Cordis 的学术争议,简单一提

一份描述 Cordis 形式化模型的配套研究文章在 dsh 发布前后流传,在开发者讨论区引发了一些争议——批评者认为它是把普通的工程约定包装成编程语言理论的表述方式("metatheory cosplay"),也有人认为,无论论文怎么呈现,这套底层框架设计本身确实有可论证的工程价值。这场争议争的是论文的学术呈现方式,而不是 Cordis 作为插件框架在实践中好不好用——它和真正在它之上做开发这件事关系不大,后者才是本文以及插件开发入门真正关心的内容。

FAQ

Cordis 是 DeepSeek-Harness 专属的吗?

不是——它是来自 cordiverse/Koishi 生态的一套通用插件框架,dsh 采纳了它,而不是专门为它打造的。

写 dsh 插件需要理解 Cordis 吗?

基本层面上是需要的——每个 dsh 插件都是一个 Cordis 插件。好消息是核心概念面很小:Contextapply()、可选的 inject、可选的 Config,以及 ctx.effect(),覆盖了日常插件代码的大部分场景。

Service 和插件有什么区别?

每个 Service 都由某个插件提供,但不是每个插件都提供 Service——大多数插件只是针对 ctx 注册工具、命令或 hook,并不对外暴露自己可复用的 ctx.<name> 能力。

热模块替换底层到底是怎么工作的?

它复用的正是 Fiber 状态机的正常卸载/重载转移路径——@deepseek-ai/cordis-plugin-hmr 监听源文件变化,在文件改动时把插件推过 UNLOADING → DISPOSED → LOADING → ACTIVE,这条路径和一次依赖消失又恢复所触发的路径完全一样。

关于 Cordis 论文的争议和使用 dsh 有关系吗?

基本没有——那是关于底层理论学术呈现方式的争议,不涉及框架的实际运行行为。它不会影响你写插件或跑插件的方式。

Next steps

想了解这些原语如何组合成 dsh 完整的扩展点全景图,阅读DeepSeek-Harness 架构:一切皆插件。想看用 Contextinjectctx.effect() 写出的实战插件代码,参见插件开发入门。想查阅"能力接缝"、"Fiber"等术语,看术语表;想浏览基于这套框架构建的工具类插件,去开发与运行时分类页。