Skip to content

用五个概念理解 Cordis

Cordis 是 dsh 底层的内嵌(vendored)插件框架。五个概念支撑起一切:

  1. 插件是实现 Service 的对象——一个带可选 injectapply(ctx) 字段的函数,或一个 Cordis 挂载到当前上下文中的 Service 子类。
  2. 上下文是服务的仓库——服务认领一个稳定的 ctx.<key>,如 ctx.toolsctx.llm
  3. 通过 inject 声明依赖——插件列出它需要的服务并等待其就绪。
  4. 用类型化事件通信——通过 TypeScript 声明合并声明,以 emitwaterfallparallelserial 分发。
  5. 注册项是可逆副作用——通过 ctx.effect()ctx.on() 安装,使重载与拆除时可预期地展开。

产品的每个部分都是插件,包括模型适配器、工具注册表、session 日志乃至 agent 循环本身——因此每个部分都可以从配置替换。

框架位于 vendor/ 下,重命名为 @deepseek-ai/ 作用域(fork 自 cordis / @cordisjs/*)。每个都是独立的 workspace 包:

内嵌包版本角色
@deepseek-ai/cordis核心:ContextService、fiber、ctx.effect、类型化事件
@deepseek-ai/cordis-plugin-loader运行时插件加载器:持有 EntryTree、导入模块、应用配置、保持图同步
@deepseek-ai/cordis-plugin-include文件驱动的加载树:把 YAML/JSON 文件读成条目、应用补丁、写回
@deepseek-ai/cordis-plugin-group把 provider 与它的消费者放进同一个 isolate 领域的行
@deepseek-ai/cordis-plugin-hmr由 loader 管理的插件的热模块替换
@deepseek-ai/cordis-plugin-logger-console控制台日志
@deepseek-ai/cordis-plugin-timer可感知销毁的定时器(ctx.timeoutctx.interval
@deepseek-ai/schemastery类型驱动的 schema 校验器(配置校验)
@deepseek-ai/cosmokit共享工具集

服务与 ctx

服务是安装在 ctx.<key> 上的值。消费者 inject 它而不是 import 具体类。例如 packages/core/agent-loop/src/index.ts 声明:

ts
export class AgentLoop extends Service implements AgentFactory {
  static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']
}

由于 dsh 通过声明合并把 Service 本体安装进 @deepseek-ai/cordis,整个产品共享同一个上下文。packages/core/* 对照表(见 架构一览)列出主干 key——ctx.sessionsctx.systemPromptctx.toolsctx.agentsctx.agentLoop——而各组中包所属的接缝还覆盖 ctx.llmctx.fsctx.shellctx.terminalsctx.subprocessctx.sandboxctx.commandsctx.jobs 以及几十个更多。

类型化事件与分发模式

每个事件只有一种分发模式,由对应方法分发:

模式是否等待分发顺序返回值
emit监听者按注册顺序观察
waterfall监听者按注册顺序观察
parallel所有监听者并行观察
serial监听者按注册顺序观察

事件通过 TypeScript 模块增强声明,带 @mode 标记,让生成的目录可以把声明与分发点对照检查。例子来自 packages/core/agent/src/runtime-types.ts

ts
declare module '@deepseek-ai/cordis' {
  interface Events {
    /** … @mode waterfall */
    'agent/pre-step'(this: Scoped<Agent>, payload: {
      agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal;
    }, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>
  }
}

Waterfall 是环绕式中间件。监听者收到 (...args, next);调用 next() 委托被包裹的结果;不调用 next() 直接返回则短路。新 harness 事件用 @mode 记录模式。

可逆副作用与插件生命周期

每个注册项都应有一个 disposer——要么由 ctx.effect() 返回,要么由 Cordis 辅助函数处理。Cordis 把每个插件跟踪在一条 fiber 上;插件挂载时执行其 apply(ctx),卸载或重载时,通过该上下文安装的每个副作用按相反顺序展开。这就是为什么 profile 重载(HMR)与完整拆除行为可预期:prompt 段、工具 schema、监听者与 provider 都随其所属插件一起消失。

loader 把配置应用到一行,并通过 ctx.loader.await() 让插件图 settle;dsh 的 assertEntriesActivatedpackages/boot/app-boot/src/index.ts)在已 settle 的树中,若某个启用条目没有 fiber 或仍处于 pending,则拒绝该树。

配置行与 schemastery 校验

loader 配置是一个行列表;每行命名一个插件并携带 config。include 插件解析 YAML/JSON 方言,!!js 标量变成 Loader 针对条目注入就绪上下文插值的表达式节点。补丁id 瞄准一行并替换其整个 config,或插入新行。

每个可配置插件通过 schemastery 校验其行。例如 AgentLoop.Config

ts
static Config = z.object({
  maxParallelToolCalls: z.number().step(1).min(1).default(DEFAULT_MAX_PARALLEL_TOOL_CALLS),
  agents: z.array(z.object({
    id: z.string().required(), sessionId: z.string().min(1), provider: z.string(),
    model: z.string(), maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER),
    cwd: z.string(), resumeSessionId: z.string(),
  })).default([]),
}) as z<Config>

z 就是 schemastery——既可作校验器也可作构造器,可通过 Schema.extend() 扩展,且可跨环境序列化/水合。

dsh 如何包装 Cordis:host runner 与 client runner

扩展系统有"面向模型"的一半和浏览器可见的一半:

  • @deepseek-ai/dsh-cordis-host-runner(提供 ctx.dynamicCordisRunner)——动态包的 host 半:定义注册表、为 cordis-dynamic 组 fiber 下的 host 半提供 node:vm 沙箱、invoke-handler 表,以及 request-run 往返。纯 host 包在本进程内运行;带浏览器半的包发出 cordis/request-run、挂起,并等待人来允许或拒绝它。
  • @deepseek-ai/dsh-cordis-client-runner——加载进页面的浏览器半(通过包的 dshClient 声明);其 host 侧 apply() 为空,因此该行出现在 host 配置中而逻辑由浏览器半承载。
  • @deepseek-ai/dsh-tool-cordis——在 ctx.tools 上注册面向模型的工具:cordis_inspect_listcordis_inspect_querycordis_inspect_selfcordis_definecordis_runcordis_stopcordis_undefine,外加一个可读改 harness 检出自身的自引用工具集。
  • @deepseek-ai/dsh-ui-cordis——浏览器界面:操作每个定义的框架级面板与只读的 define 卡片。

仓库中的一个最小插件形态

普通(非动态)插件的形态就是 Cordis 的形态。web-app 命令行 provider packages/bundle/web-app/src/startup.ts 具有代表性:

ts
export const name = 'web-startup'
export const inject = ['cmdlineArgs']
export const WEB_STARTUP_SERVICE = 'webStartup'

export function apply(ctx: Context): void {
  const program = new Command() // 为 --host/--port/--trusted-host 准备的 commander 程序
  ctx.provide(WEB_STARTUP_SERVICE, { … })
}

tool-cordis 通过产品 API 注册工具(packages/extensions/tool-cordis/src/index.ts):

ts
export const name = 'tool-cordis'
export const inject = ['tools', 'systemPrompt', 'dynamicCordisRunner', 'cordisInspect']

export function apply(ctx: Context): void {
  ctx.systemPrompt.section({ name: 'tool:cordis', order: 115, text: CORDIS_SYSTEM_PROMPT })
  ctx.tools.register(defineTool({
    name: 'cordis_inspect_list',
    description: 'List every Cordis Inspect Provider …',
    parameters: {},
    execute(_args, _exec) {
      return Promise.resolve({ providers: ctx.cordisInspect.list() })
    },
  }))
}

通过 apply(ctx) 注册的一切都是作用域/副作用绑定的:行卸载时,prompt 段与工具注册项随之展开。

dsh-plugin 生态

"树外插件"指通过 dsh plugin --profile <name> add <pkg>apps/cli/src/plugin.ts 中的轻量 pnpm 转发器)安装进 profile 的包。只有当其 package.json 声明 dsh.bundle.patch 时它才成为 profile 层。想成为 Cordis 插件行的插件,是任何 Cordis 能挂载其默认导出(apply/Service)的包;想成为可复用 bundle,它还需附带 cordis.patch.ymldsh.bundle 清单字段。盒内插件通过修复后的扁平 profiles/node_modules 回退访问,因此树外同级解析到安装的单一 Cordis 实例。

延伸阅读

  • Cordis 入门——同一组五个概念的更深入导览(仓库中的 docs/cordis-primer.md)。
  • 运行时与 Agent 生命周期——事件与副作用如何驱动真实 agent。
  • 启动流程与 CLI——行与补丁如何变成运行中的树。
  • 仓库文档:docs/cordis-primer.mddocs/cordis-tutorial/index.mddocs/subsystems/extensions.md
  • 内嵌源码与 README:vendor/README.mdvendor/cordis/vendor/loader/vendor/include/vendor/schemastery/
  • 扩展包:packages/extensions/README.md 以及 packages/extensions/ 下每个包的 README.md