第三章:Cordis 框架基础
本章目标:帮助你理解 Cordis 是什么、Plugin/Service/Context 模型如何工作、
ctx.effect()和ctx.on()的注册机制、Waterfall 语义的细节、以及生命周期管理。阅读本章后,你应该能编写一个简单的 Cordis 插件并理解 dsh 中每个插件的挂载原理。
3.1 什么是 Cordis
Cordis 是一个上下文驱动的插件框架,它是 DeepSeek Harness 的底层基础设施。dsh 中的每一个功能——从模型适配器到工具注册、从会话日志到 Agent 循环本身——都是通过 Cordis 插件实现的。
核心设计原则:
没有特权核心:你通过配置来扩展 dsh,而不是通过修改核心代码
Registrations are effects:所有注册都是可逆的效果
声明式依赖:通过
inject声明依赖,加载顺序由依赖关系自动推导
3.2 五个核心概念
3.2.1 Plugin(插件)
Plugin 是 Cordis 的基本单元。它可以是两种形式之一:
函数形式:
类形式(Service 子类):
3.2.2 Context(上下文)
Context 是服务的仓库。每个插件通过 ctx.<key> 访问服务,而不是通过导入具体实现。
关键理解:当你读取 ctx.tools 时,Cordis 通过 Proxy 的 get trap 来解析实际的服务实例。
3.2.3 Service(服务)
Service 是暴露在 Context 上的 API。通过 ctx.provide() 或继承 Service 类来注册。
已知的服务键:
| ctx 键 | 拥有包 | 用途 |
|---|---|---|
ctx.sessions | core/session | 会话事件日志和内存存储 |
ctx.systemPrompt | core/system-prompt | Prompt 段落和工具 schema 组装 |
ctx.tools | core/tools | 带范围的工具注册表和受保护执行管道 |
ctx.agents | core/agent | Agent 接口、活注册表和 agent/* 事件 |
ctx.agentLoop | core/agent-loop | 默认驱动实现 |
ctx.llm | llm/llm | 消息和流词汇表 + 适配器接缝 |
3.2.4 inject(依赖声明)
通过 inject 声明服务依赖,Cordis 等待这些服务存在后才加载插件。
加载顺序由依赖关系推导,而不是手动编排。
3.2.5 Registration is an Effect(注册是效果)
所有注册都是可逆的效果。当插件卸载时,所有注册自动撤销。
3.3 事件派发模式
Cordis 提供五种事件派发模式,每种有不同的语义:
3.3.1 emit(同步广播)
同步执行所有监听者,不等待,不返回值。
用途:通知观察者,如日志记录。
3.3.2 waterfall(瀑布/中间件)
关键规则:
监听者接收
(...args, next)必须调用
next()来委托,否则短路next()返回下游结果
dsh 中的 waterfall 事件:
| 事件 | 用途 |
|---|---|
agent/pre-step | 决定模型看到什么 |
agent/request | 拦截/修改 LLM 请求 |
llm/stream | 拦截/修改模型流 |
tools/pre-execute | 工具执行前拦截 |
tools/execute | 工具执行 |
tools/post-execute | 工具执行后拦截 |
3.3.3 parallel(并行)
异步执行所有监听者,等待全部完成。
用途:扇出操作,如并发初始化。
3.3.4 serial(串行)
按注册顺序异步执行监听者,直到某个返回 bail 值。
用途:有序执行,如依次尝试策略。
3.3.5 bail(短路)
同步执行监听者,直到某个返回 bail 值(非 null/undefined/false)。
用途:策略决策,如权限检查。
派发模式对比
| 模式 | 等待? | 派发顺序 | 返回值? | 典型用途 |
|---|---|---|---|---|
emit | 否 | 注册顺序 | 无 | 通知观察者 |
waterfall | 否 | 注册顺序 | 有 | 中间件链 |
parallel | 是 | 并行 | 无 | 扇出 |
serial | 是 | 注册顺序 | 有 | 有序执行 |
bail | 否 | 直到 bail | 有 | 策略短路 |
3.4 Waterfall 语义详解
Waterfall 是 dsh 中最重要的派发模式。它实现了一个环绕中间件(around-middleware)模式。
3.4.1 基本流程
3.4.2 实际例子
3.4.3 短路的后果
不调用 next() 意味着:
下游监听者不会执行
最终行为不会执行
返回当前监听者的结果
必须调用 next() 的原因:
如果不调用,整个链被中断
模型请求可能无法发出
工具可能无法执行
3.5 生命周期管理
3.5.1 Fiber(光纤)
Cordis 中的每个插件运行在一个 Fiber 中。Fiber 管理插件的挂载、激活、卸载和清理。
3.5.2 Fiber 状态
| 状态 | 说明 |
|---|---|
Created | 刚创建 |
Pending | 等待依赖 |
Loading | 正在加载 |
Active | 活跃运行 |
Unloading | 正在卸载 |
Disposed | 已清理 |
Failed | 加载失败 |
3.5.3 Disposer(清理器)
ctx.effect() 返回的清理函数在 Fiber 卸载时按反向注册顺序执行。
3.5.4 作用域隔离
3.6 实际例子:编写一个简单的插件
注册到 Profile
3.7 Loader 与配置
3.7.1 cordis.yml
cordis.yml 是 Cordis 的配置文件。Loader 解析它并挂载插件。
3.7.2 !!js 表达式
配置中支持 !!js 表达式,用于动态配置:
3.7.3 Patch 层
Patch 层按顺序应用,覆盖或添加配置行:
每个 Patch 目标是一个 entry,替换其整个配置或插入新行。
3.8 小结
| 概念 | 一句话解释 |
|---|---|
| Plugin | Cordis 的基本单元,函数或 Service 子类 |
| Context | 服务的仓库,通过 Proxy 解析 |
| Service | 暴露在 Context 上的 API |
| inject | 声明依赖,自动推导加载顺序 |
| Registration | 可逆的效果,卸载时自动清理 |
| Waterfall | 环绕中间件,必须调用 next() 委托 |
| Fiber | 插件的生命周期容器 |
| Disposer | 反向注册顺序执行的清理函数 |
下一步:第四章:核心与 Agent 循环——深入理解 Agent、AgentLoop、Turn/Step 生命周期、Cancel 机制和 Scope 模型。