第 3 章 Cordis 核心概念

在阅读 dsh 的子系统文档之前,必须先掌握 Cordis 的核心概念。本章对应官方 Cordis 入门(cordis-primer)。

3.1 五个核心概念

① 插件是实现 Service 的对象

插件有三种形态(均可被 Cordis 挂载):

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

// 1. 函数插件(最常见):Cordis 直接调用该函数
export function apply(ctx: Context) {}

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

// 3. 类插件:Service 子类(需要公开服务时使用)
export class MyService extends Service {
  constructor(ctx: Context) {
    super(ctx, 'myService')
  }
}
  • 函数/对象插件中,name 导出是可选的显示元数据,用于诊断信息。

  • 在你需要公开服务之前,请一直使用函数形态。

② 上下文是服务的容器

一个服务占据一个稳定的 ctx.<key>(如 ctx.tools、ctx.llm、ctx.sessions)。其他插件通过 key 查找服务,而非导入具体实现——因此配置可以自由替换提供方,消费方代码不用改。

③ 通过 inject 声明服务依赖

export const inject = ['tools']
export function apply(ctx: Context) {
  // 走到这里时 ctx.tools 一定已就绪
}

插件声明所需服务后会等待这些服务就绪才启动(在此之前处于 PENDING 状态)。加载顺序由服务依赖表达,而非手动编排启动序列,也不是配置文件里的书写顺序。

④ 类型化事件用于通信

服务通过 TypeScript 声明合并注册事件名,然后以 5 种方式分发(见 3.2)。事件让插件在互不知晓对方存在的前提下协作。

⑤ 注册是可逆的副作用

提示词片段、工具 schema、适配器、提供方、监听器……都通过 ctx.effect() 或 ctx.on() 安装,reload 和 teardown 时会自动撤销。每个注册都应有对应的 disposer:要么从 ctx.effect() 返回一个,要么使用内置 API 自带的自动管理。

3.2 五种事件分发模式

每种事件有且只有一种分发模式,这是事件公开约定的一部分:

模式是否 await分发顺序是否有返回值
emit否按注册顺序观察否
waterfall否按注册顺序包装是
parallel是所有监听器并行否
serial是按顺序执行,首个非空返回值胜出并停止是
bail否serial 的同步版本,停在首个 bail 值是

调用方式:

ctx.emit('evt', ...args)                    // 同步广播,不等待
await ctx.parallel('evt', ...args)          // 并行,共同等待
await ctx.serial('evt', ...args)            // 顺序执行
ctx.bail('evt', ...args)                    // 同步 bail
await ctx.waterfall('evt', ...args, next)   // 瀑布(见 3.3)

新的 harness 事件通过 @mode 标签记录模式,生成的文档目录可将声明与分发调用点交叉校验。每个 harness 事件的模式都记录在其所属子系统页面的自动生成参考中。

3.3 Waterfall 语义:环绕中间件

ctx.waterfall 是环绕中间件,是实现拦截的核心模式:

  • 监听器签名:(...args, next)。

  • 调用 next() 会执行下游监听器;下游返回值通过 next() 返回到当前包装层,可被该层包装后继续向外返回。

  • 不调用 next() 直接返回 = 短路(否决),下游监听器和默认逻辑都不会运行。

  • 协作式监听器通常修改一个共享的请求/决策对象后调用 next() 委托;监听器也可以完全替换结果。

  • 仅当监听器必须在普通注册之前运行时才使用 prepend: true。

纪律:只负责观察或标注的 waterfall 监听器必须调用 next()。一个日志监听器若忘记委托,会悄无声息地吞掉所有下游默认行为——这是本仓库的常设规则。

对于"单决策"事件,短路是设计意图:策略监听器在拥有决策权时可以不调用 next() 直接返回。

harness 中的典型 waterfall:

  • agent/request:允许插件替换模型调用配置。

  • approval/request:允许审批策略代替用户作答。

  • agent/pre-step、llm/stream、tools/pre-execute、tools/execute、tools/post-execute。

另外,agent/turn-stopping 是 serial 事件(没有 next())。

3.4 Loader 配置:!!js 表达式

@deepseek-ai/cordis-plugin-include 将 !!js 标签解析为表达式节点:

  • Loader 在插件声明的注入激活后,基于该插件上下文(ctx.serviceName)插值条目的 config。

  • 条目的 disabled 字段在每次挂载决策时基于 loader 上下文求值(可按平台/环境门控一行配置)。

  • !!js 仅在 config 与 disabled 字段内有效;其余元数据(name、id、inject 等)保持字面值。

3.5 实践规则(官方建议)

把行为封装为插件:工具流水线事件属于 ctx.tools,模型流式输出属于 ctx.llm,实时 agent 协调属于 ctx.agents。拦截和策略优先使用事件;直接能力调用优先使用服务方法。

每个注册都应有对应的 disposer。如果 teardown 顺序有要求,把相关工作放在同一个 effect 中——因为 disposer 按注册逆序启动,但多个异步 disposer 是并发运行的,只有放在同一个 disposer 里依次 await 才能保证顺序。


依据官方 Cordis 入门 整理。

DeepSeek Harness / 第 3 章 Cordis 核心概念 0 字 0 行 cosolar
2026-09-17T10:12:13.864183453Z 2026-09-17T10:39:02.864835657Z