Cordis:时空可组合性元框架

一句话定位

A Meta-Framework of Spatiotemporal Composability——一个让插件可以”随时插拔、按依赖自动组装”的 TypeScript 插件元框架。它是 DeepSeek Harness(dsh)的底座:dsh 里的一切(LLM 适配器、工具注册、会话、agent loop 本身)都是 Cordis 插件,“组合方式”本身是数据(cordis.yml)。

值得快速了解的理由:

  • 论文级形式化:配套 92 页论文《A Programming Paradigm for Spatiotemporal Composability》(DeepSeek × 北大,arXiv:2608.25512)
  • 生产验证:同一设计已在 Koishi 聊天机器人框架运行 4 年,4000+ 社区插件在生产环境验证过
  • 是理解 DeepSeek Harness “一切皆插件、无特权核心、自进化”架构的钥匙

背景与来历

维度事实
作者Yifan Shi(北大 / DeepSeek-AI,即 Koishi 作者 Shigma)、Wei Zhang(北大)、Tianyi Cui(DeepSeek-AI)
版本cordis 4.0.0-rc.8(API 未稳定,可能随时变化),MIT
体量核心 packages/core 仅约 1,850 行 TypeScript
前身Koishi 的插件内核(跑了 4 年)抽出来独立成库
现状被 DeepSeek Harness 以 vendor 方式内置(vendor/cordis),官方文档托管在 dsh 文档站
数据7,847 stars / 477 forks(2026-08-31)

论文要解决的痛点:绝大多数插件系统(包括 VSCode)插件一旦加载就无法运行时单独卸载,卸载必须重启整个宿主进程——论文统计 VSCode Marketplace Top 100 扩展中 87 个含可执行代码、启用后无法运行时卸载。对 Agent harness 而言这不可接受:agent 需要自进化——运行时增删工具、替换适配器、修改自身,而不能每次都重启。

核心思想:时空可组合性

论文把”动态组合”拆成两个正交维度:

  • 时间可组合性(Temporal)——可逆 effect(revertible effects):每个上下文变换都携带逆变换,由运行时追踪;组件被移除时,其全部副作用被完全回滚。工程落地为一条朴素纪律:任何注册都返回一个 disposer,卸载即逆序出栈。
  • 空间可组合性(Spatial)——响应式 coeffect(reactive coeffects):组件声明自己依赖什么(inject),上下文的每次变化按依赖规格通知组件——依赖就绪则加载,依赖消失则卸载,依赖恢复则复活。

数学根基是类型论中经典的 effect / coeffect(代数效应与处理器)概念提升为运行时机制,二者统一为一个 context 类型,构成一套编程范式;再组合出 component 概念与动态组合演算(calculus),把单组件的可组合性推广到整个交错系统。

传统框架是”你在我的框架里写业务”;Cordis 是”框架本身就是一堆可以互相替换的插件”——连 logger、timer、事件系统都和业务插件地位平等。

五个核心概念(速查)

  1. 插件是实现 Service 的对象。三种形态:函数(最常见,导出 apply(ctx))、对象(带 apply 方法)、类(Service 子类)。可选导出 name(诊断标识)、inject(依赖)、Config(配置 schema)。
  2. 上下文(Context)是服务的容器。一个服务占据稳定的 ctx.<key>(如 ctx.tools、ctx.llm);消费方按 key 查找而非 import 实现,因此配置层可以换提供方而不改消费方。
  3. inject 声明服务依赖。插件保持 PENDING 直到所列服务全部就绪;加载顺序由依赖表达,而非 YAML 里的书写顺序。依赖跟踪是持续的:运行中服务消失,依赖它的插件跟着卸载;服务恢复,再自动加载。
  4. 类型化事件用于通信。通过 TS 声明合并(interface Events)注册事件名与签名,五种分发模式见下表。
  5. 注册是可逆的副作用。ctx.on()、ctx.plugin()、服务注册、harness 注册表(如 ctx.tools.register)本身已是 effect,卸载自动撤销;框架不管理的资源(定时器/连接/watcher)要自己包进 ctx.effect() 并返回 disposer。

事件分发模式

模式调用语义
emitctx.emit(name, ...args)同步广播,不等待、不收集返回值
parallelawait ctx.parallel(...)所有监听器并发运行,一同等待
serialawait ctx.serial(...)按序执行;第一个非 null/false/undefined 返回值胜出并短路
bailctx.bail(...)serial 的同步版
waterfallctx.waterfall(name, ...args, next)环绕中间件:监听器收到 (...args, next),可包装 next() 的返回值,也可不调 next() 直接返回(否决/短路)

waterfall 纪律:只观察/标注的监听器必须调用 next();不调就是有意短路。harness 用它做 agent/request(替换模型调用配置)、approval/request(策略代替用户作答)等决策事件。

最小可运行示例

// hello.ts —— 插件就是一个函数
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello'
export function apply(ctx: Context) {
  console.log('hello from my first plugin')
}
# cordis.yml —— 应用 = 一份插件组合数据
- name: './hello.ts'

带依赖的例子:

// consumer.ts
export const name = 'consumer'
export const inject = ['greeter']        // 等 greeter 服务就绪才启动
export function apply(ctx: Context) {
  console.log(ctx.greeter.greet('world'))
}

提供方用 Service 子类(运行时 super(ctx, 'greeter') 注册 + 编译时 declare module 声明合并给 ctx.greeter 上类型)。去掉提供方后消费方安静地停在 PENDING——不崩溃、不半运行。

Fiber 状态机

每个已加载插件实例有一个 fiber(运行时句柄):

PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED
                 ↘ FAILED
  • PENDING:已声明,等待所需服务(合法状态,不是错误)
  • FAILED:apply 或配置校验抛异常
  • fiber.dispose() 等待所有清理(含异步 disposer)完成,并递归卸载子插件
  • disposer 按注册逆序启动;多个异步 disposer 并发运行——有顺序要求的拆除放进同一个 disposer 内依次 await
  • “插件没输出”的第一排查项:遍历 ctx.registry 看 fiber 是否卡在 PENDING(缺服务)

核心实现在 packages/core/src/fiber.ts(486 行):ctx.effect(execute, label) 的执行结果归一为四种形态——函数/可舍弃值直接收集、Promise 用 then 收集、同步迭代器逐项收集、异步迭代器逐项 await(每轮检查 epoch 是否失效)——回收时统一转成一串 disposer。

配置与 HMR

  • 配置校验:插件导出 Config(Standard Schema 验证器,dsh 用 Schemastery),apply(ctx, config) 前校验,坏配置直接 FAILED 并给出精确报错(如 $.targets expected array but got not-an-array),插件绝不会带着不完整配置启动。
  • 条目元数据:id(稳定标识,让 loader 区分”修改”与”删了再加”)、disabled: true(保留条目但卸载插件)、config、组(嵌套子列表整体加载/卸载)、isolate(给组内某服务名提供独立实例,两组互不影响)。
  • HMR:卸载释放 effect + 依赖驱动加载,所以热替换 = 先卸载再加载。@cordisjs/plugin-hmr 监视文件,保存即完成旧实例回卷 → 新代码加载。编辑 cordis.yml 也触发:按 id diff,只动变化的条目(没有 id 的条目每次读取都获得新 id,会被视为删了重挂)。

仓库结构与源码地图

packages/
├── core/            # 全部内核,约 1,850 行 TS
│   └── src/
│       ├── fiber.ts      (486 行) ★ effect 追踪 + fiber 状态机,必读
│       ├── reflect.ts    (281 行) ctx 属性访问代理(服务解析/隔离)
│       ├── utils.ts      (278 行) DisposableList 等
│       ├── logger.ts     (246 行) logger 服务
│       ├── registry.ts   (214 行) 插件注册表(Plugin.Runtime)
│       ├── events.ts     (178 行) 五种分发模式
│       ├── service.ts    (80 行)  Service 基类
│       └── context.ts    (78 行)  Context:extend/isolate/intercept
├── loader/          # 声明式加载器:读 cordis.yml、配置协调
├── include/         # YAML !!js 表达式节点
├── hmr/             # 热模块替换插件
├── group/           # 组(嵌套条目单元)
├── timer/ logger-console/  # 基础设施插件(本身也是插件,体现无特权核心)
├── create/          # 脚手架(create-cordis)
└── utils/

源码阅读顺序建议:context.ts(容器模型)→ service.ts + registry.ts(服务注册与依赖)→ events.ts(分发模式)→ fiber.ts(effect 归一化与拆除,最核心)。

论文要点

《A Programming Paradigm for Spatiotemporal Composability》(cordiverse/paper,2026-08-26 提交 arXiv:2608.25512,92 页):

  1. 识别动态组合的两个正交维度:时间(移除时完全回滚副作用)与空间(声明式依赖的响应式管理)
  2. 把经典 effect/coeffect 提升为运行时机制:revertible effects(每个上下文变换携带运行时追踪的逆变换)+ reactive coeffects(上下文变化按 coeffect 规格通知组件)
  3. effect context 与 coeffect context 统一为单一 context type,构成编程范式
  4. 组合出 component 概念与动态组合演算(9 规则 + fiber 状态机),元理论五条定理把时空可组合性从单组件推广到交错组件系统:Preservation / Recovery exactness(撤销精确)/ Ordering+Coherence(依赖有序)/ Progress(无环必终止)/ Confluence(热改收敛到干净安装态)
  5. 实现即 Cordis:核心库(effect tracking + coeffect resolution)+ 声明式组件加载器(配置协调 + HMR)

深度分析见 2608.25512_spatiotemporal-composability。

学习路径(推荐顺序)

  1. 本页 → 建立概念地图(30 分钟),配合 使用教程 边学边练
  2. Cordis 教程 7 讲(deepseek-harness 仓库 docs/cordis-tutorial/,有中文版,每章是可运行示例): 第一个插件 → 生命周期与 effect → 服务 → 事件 → 配置 → 组合与 HMR → 进入 harness(注册真实工具)
  3. Cordis 入门(docs/cordis-primer.md/.zh.md):一页概念参考,含 waterfall 语义与 loader 配置细节
  4. 源码:按上文地图读 packages/core(总量不到 2,000 行,半天可读完)
  5. 论文:先读 2608.25512_spatiotemporal-composability 深度解析(五条元定理/演算规则/理论↔实现对照),再啃 92 页原文;CSDN 上有社区实现剖析(搜 “Cordis 算法与实现剖析”)可对照

教程环境:git clone deepseek-ai/deepseek-harness && pnpm install,在 tmp/cordis-tutorial/ 下用 node --import tsx ../../vendor/cordis/bin.js 运行每章示例(启动器创建根 Context、挂 Loader、读当前目录 cordis.yml)。

资源