Skip to content

1.1 一切皆插件:Cordis 框架

本章概览:理解 DSH 的地基——Cordis 插件框架的五个核心概念(插件、上下文、服务、类型化事件、可逆注册),以及"注册即副作用"这一贯穿全项目的工程纪律。

Cordis 是什么

Cordis 是一个 TypeScript 插件框架,为需要显式依赖注入、作用域服务、生命周期管理的清理和可选配置驱动加载的应用而设计。DSH 将其 vendored(内置副本)进仓库的 vendor/ 目录,作为整个 Harness 的骨架。

出处:packages/../vendor/README.md(vendoring 说明)与 docs/cordis-primer.md

官方入门把 Cordis 概括为五个概念

概念一句话
插件(plugin)实现 Service 的对象:函数插件(apply(ctx))或 Service 子类
上下文(context)服务的仓库;服务通过 ctx.<key> 被发现
注入(inject)插件声明所需服务,等待其出现而非手工排启动顺序
类型化事件(typed events)服务通过 TS 声明合并声明事件,按 emit/waterfall/parallel/serial 派发
可逆注册(reversible effects)提示片段、工具 schema、适配器、提供方、监听器都通过 ctx.effect() / ctx.on() 安装,卸载时撤销

出处:docs/cordis-primer.mddeepseek-harness-src/docs/cordis-primer.md

插件的三种形态

ts
import { Service, type Context } from '@deepseek-ai/cordis'

// 1. 函数插件(最常见)
export function apply(ctx: Context) {}

// 2. 对象插件
export const objectPlugin = { name: 'object-plugin', apply(ctx: Context) {} }

// 3. 类插件:Service 子类(需要公开服务时使用)
export class MyService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'myTutorialService')
  }
}

Cordis 加载模块时用上下文调用 apply,插件通过 ctx 注册自己贡献的一切;name 导出是可选的显示元数据,用于诊断。

出处:docs/cordis-tutorial/01-first-plugin.zh.mddeepseek-harness-src/docs/cordis-tutorial/01-first-plugin.zh.md

上下文与服务:通过 key 而非 import 通信

一个服务在一个 ctx.<key> 上声明自己,例如 ctx.toolsctx.llmctx.sessions。其他插件按 key 发现服务,而不是 import 具体实现——这使实现可以整体替换而不影响消费者。DSH 的核心服务即由各包贡献:

ctx服务归属包
ctx.sessions会话日志与内存存储core/session
ctx.systemPrompt提示词片段与工具 schema 组装core/system-prompt
ctx.tools工具注册表与把关执行流水线core/tools
ctx.agentsAgent 接口、活跃注册表、agent/* 事件core/agent
ctx.agentLoop实现 Agent 接口的默认驱动器core/agent-loop
ctx.llmLLM 消息/流式词汇表与适配器 seamllm/llm

出处:docs/architecture.mddeepseek-harness-src/docs/architecture.md

依赖注入:inject 表达加载顺序

插件通过 inject 字段声明所需服务。Loader 会等待这些服务存在后再挂载该插件,因此加载顺序由服务依赖表达,而不是配置文件中条目的位置(配置项是并发启动的)。

yaml
# 语义示例:该插件需要 ctx.sessions 与 ctx.tools 就绪后才激活
- name: 'my-plugin'
  inject: ['sessions', 'tools']

出处:docs/cordis-primer.mddocs/cordis-tutorial/03-services.md

注册即副作用:effect 与 disposer

Cordis 建立的任何注册都属于 effect,在所属插件卸载时撤销。对于 Cordis 未管理的资源(定时器、连接、watcher),必须包装进 ctx.effect() 并返回 disposer:

ts
ctx.effect(() => {
  const timer = setInterval(() => console.log('tick'), 200)
  return () => {
    clearInterval(timer)
    console.log('cleaned up')
  }
})

DSH 把这条纪律上升到仓库级约定:"Registrations are effects: every contribution goes through ctx.effect() / ctx.on(); a registry's register() returns the disposer"(见 AGENTS.md)。在 DSH 里,几乎每个服务 API 的 registerXxx() 都返回 disposer,例如 ctx.tools.register(def)ctx.llm.registerAdapter(...)ctx.subagents.registerProvider(...)

出处:docs/cordis-tutorial/02-lifecycle-and-effects.zh.mdAGENTS.md

为什么"一切皆插件"是强主张

在 DSH 中这不是口号,而是架构约束

  1. 没有特权内核:连 agent loop 本身都是插件(core/agent-loop),没有需要打补丁的内核;
  2. 可替换性:任何插件都能被配置替换(patch 层覆盖);
  3. 作用域化:同一服务可以按 agent 作用域分别注册(dsh-scope),一个子代理可以拥有不同的工具集;
  4. 可回收:所有注册随插件卸载撤销,热重载(HMR)不会泄漏。

官方文档对此的表述是:

"产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop 本身,因此每一部分都可以从配置替换。不存在需要打补丁的特权内核。"

出处:docs/architecture.md

小结

Cordis 提供骨架:插件贡献服务与事件,上下文是服务仓库,注入表达依赖,effect 保证可逆。下一章 1.2 事件系统与四种派发模式 讲解插件的通信与拦截机制——这是 DSH 几乎所有扩展点的实现方式。

参考资料

本文档为学习用途的原创讲解,基于 MIT 许可的开源项目 deepseek-ai/deepseek-harness 编写;所有引用均注明出处。