Appearance
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.md(deepseek-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.md(deepseek-harness-src/docs/cordis-tutorial/01-first-plugin.zh.md)
上下文与服务:通过 key 而非 import 通信
一个服务在一个 ctx.<key> 上声明自己,例如 ctx.tools、ctx.llm、ctx.sessions。其他插件按 key 发现服务,而不是 import 具体实现——这使实现可以整体替换而不影响消费者。DSH 的核心服务即由各包贡献:
ctx 键 | 服务 | 归属包 |
|---|---|---|
ctx.sessions | 会话日志与内存存储 | core/session |
ctx.systemPrompt | 提示词片段与工具 schema 组装 | core/system-prompt |
ctx.tools | 工具注册表与把关执行流水线 | core/tools |
ctx.agents | Agent 接口、活跃注册表、agent/* 事件 | core/agent |
ctx.agentLoop | 实现 Agent 接口的默认驱动器 | core/agent-loop |
ctx.llm | LLM 消息/流式词汇表与适配器 seam | llm/llm |
出处:
docs/architecture.md(deepseek-harness-src/docs/architecture.md)
依赖注入:inject 表达加载顺序
插件通过 inject 字段声明所需服务。Loader 会等待这些服务存在后再挂载该插件,因此加载顺序由服务依赖表达,而不是配置文件中条目的位置(配置项是并发启动的)。
yaml
# 语义示例:该插件需要 ctx.sessions 与 ctx.tools 就绪后才激活
- name: 'my-plugin'
inject: ['sessions', 'tools']出处:
docs/cordis-primer.md与docs/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.md与AGENTS.md
为什么"一切皆插件"是强主张
在 DSH 中这不是口号,而是架构约束:
- 没有特权内核:连 agent loop 本身都是插件(
core/agent-loop),没有需要打补丁的内核; - 可替换性:任何插件都能被配置替换(patch 层覆盖);
- 作用域化:同一服务可以按 agent 作用域分别注册(
dsh-scope),一个子代理可以拥有不同的工具集; - 可回收:所有注册随插件卸载撤销,热重载(HMR)不会泄漏。
官方文档对此的表述是:
"产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop 本身,因此每一部分都可以从配置替换。不存在需要打补丁的特权内核。"
小结
Cordis 提供骨架:插件贡献服务与事件,上下文是服务仓库,注入表达依赖,effect 保证可逆。下一章 1.2 事件系统与四种派发模式 讲解插件的通信与拦截机制——这是 DSH 几乎所有扩展点的实现方式。
参考资料
- docs/cordis-primer.md — Cordis 五个概念(
deepseek-harness-src/docs/cordis-primer.md) - docs/cordis-tutorial/ — 七章动手教程(
deepseek-harness-src/docs/cordis-tutorial/) - docs/architecture.md — 核心包表(
deepseek-harness-src/docs/architecture.md) - AGENTS.md — 仓库约定(注册即副作用等)