GitHub 趋势 · 第 48 期
README 只有 25 字节,论文在另一个仓库
#插件框架#依赖注入#热重载
本期判断:cordis 是本轮月榜里动量最高的未写项目 —— 8,353 star,本月 +8,270。但它的根目录 README 全文只有 25 字节,理论单独开了一个仓库,官方文档挂在别人的文档站上。这不是摆烂,而是把三种不同的东西分别放到了它们最合适的位置。它值得读,但别指望靠一个仓库把它读懂。
插件式系统的老毛病:注册的副作用卸不干净,热重载之后旧监听器还挂着;插件之间有依赖,加载顺序却靠人手工编排,漏一个就静默失败;一个模块路径拼错,进程照跑,只是那个插件悄悄没生效。cordis 把这三件事当框架的第一性问题来解决 —— 而且它把「会静默失败」这件事也写进了文档。
它是什么
cordis 是 cordiverse 组织维护的插件框架,自述一句话:A Meta-Framework of Spatiotemporal Composability(时空可组合性的元框架)。仓库 2022 年 5 月创建,TypeScript 写成,MIT 许可,最新版本号停在 v4.0.0-rc.10,44 个未关闭 issue。
先看它最反常规的地方 —— 门面、理论、文档分别外置:
- ▪根目录 README 全文只有 25 字节,内容是一行相对路径:./packages/core/README.md —— 门面是个指针
- ▪真正的说明在 packages/core/README.md,一共三段,其中一段是硬警告
- ▪论文单独开了一个仓库 cordiverse/paper(2,979 star),arXiv 编号 2608.25512
- ▪官方文档不在自己域名下,挂在 deepseek-harness.github.io 的文档站里,且官方自述 official documentation is still under construction
也就是说,想搞懂这套框架,得同时读四个地方:25 字节的指针、packages/core 里的短说明、另一个仓库的论文、别人家的文档站。根目录 README 的实际内容就是这样一行:
./packages/core/README.md
真正的说明只有三段,其中一段是警告
把那个指针跟到底,packages/core/README.md 的正文(Markdown 原文)是这样的:
Cordis A Meta-Framework of Spatiotemporal Composability. **Cordis is under active development. The API is not yet stable and may change without notice.** - Paper: _A Programming Paradigm for Spatiotemporal Composability_ [arXiv] [repository] - Documentation: [cordis-primer] (official documentation is still under construction)
中间那句加粗的话是本期必须写出来的:官方自己说明 API 不稳定、可能变更、且不另行通知。而版本号也确实还在 rc 线上。
更值得留意的是它对 rc 的处理方式。最新 release 是 v4.0.0-rc.10,发布于 2026-09-08,但这条 release 在 GitHub 上的 prerelease 标记是 false —— 也就是版本号里写着 rc,平台却把它算作正式发布。自动升级工具如果只看这个标记,会把它当稳定版收进依赖树。
而 rc 线里确实还有不兼容变更。v4.0.0-rc.10 的 release 说明第一条就是 Breaking Changes:loader 的 EntryTree.write() 被 EntryTree.commit(change: EntryChange) 取代,子类每收到一次树上变更就会拿到一个 EntryChange,里面带着条目 id、所属分组、新 options 以及此前的 options。顺带一提,这份 release 说明是中英双语的,英文段和中文段各写一遍。
五个核心概念,写得相当克制
官方给的概念参考只有五个词条,一条一句,不铺垫:
- ▪插件就是实现 Service 的对象 —— 可以是带 apply(ctx) 的函数,也可以是 Service 子类,生命周期由 cordis 挂到当前上下文
- ▪上下文是服务的容器 —— 一个服务占一个稳定的 key(原文写作 ctx.<key>),插件靠 key 查找服务,而不是 import 具体实现
- ▪inject 声明服务依赖 —— 插件声明自己需要什么,就等这些服务就绪再启动;加载顺序由依赖表达,而不是手动编排启动序列
- ▪类型化事件负责通信 —— 服务用 TypeScript 声明合并注册事件名,再用不同方法分发;新事件通过 @mode 标签记录分发模式,好让生成的目录把声明与调用点做交叉校验
- ▪注册是可逆的副作用 —— 提示词片段、工具 schema、适配器、监听器都通过 ctx.effect() 或 ctx.on() 安装,reload 和 teardown 时按预期撤销
五种事件分发模式,方法名不能混用
每个事件只有一种分发模式,且只能用对应方法分发。官方给了一张对照表:
| 模式 | 是否 await | 分发顺序 | 有返回值 |
|---|---|---|---|
| emit | 否 | 监听器按注册顺序观察 | 否 |
| waterfall | 否 | 监听器按注册顺序观察 | 是 |
| parallel | 是 | 所有监听器并行观察事件 | 否 |
| serial | 是 | 监听器按注册顺序观察 | 是 |
| bail | 否 | 按注册顺序观察,直到某个监听器返回 bail 值 | 是 |
其中 waterfall 是环绕中间件。监听器收到的是 (...args, next),调用 next() 才执行下游;下游的返回值会通过 next() 回到当前这一层,可以包装之后继续往外传;不调 next() 直接返回就是短路。
官方对短路的说法很明确:对单决策事件,短路是设计意图。有决策权的策略监听器可以不调 next() 直接返回,只做标注或观察的监听器则必须委托下去。另外 prepend: true 只在监听器必须抢在普通注册之前运行时才用。
三种插件形态也一并给了。函数形态最常用,类形态留给要对外暴露服务的时候:
import { Service, type Context } from '@deepseek-ai/cordis'
// 1. Function plugin
export function apply(ctx: Context) {}
// 2. Object plugin: an object with an apply method.
export const objectPlugin = {
name: 'object-plugin',
apply(ctx: Context) {},
}
// 3. Class plugin: a Service subclass
export class MyService extends Service {
constructor(ctx: Context) {
super(ctx, 'myTutorialService')
}
}
上手:先写一个插件,再把它写进 YAML
官方教程的路径是在 deepseek-harness 仓库里跑一份 vendor 版启动器,而不是 npm 装一个包就完事(这件事后面单独说)。准备工作原文如下:
git clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness pnpm install mkdir -p tmp/cordis-tutorial cd tmp/cordis-tutorial
然后写两个文件。插件本体是一个函数,导出 apply,cordis 加载时会用一个上下文去调用它:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello'
export function apply(ctx: Context) {
console.log('hello from my first plugin')
}
应用本身由配置组装,就是一个 YAML 列表:
- name: './hello.ts'
之后每一章都跑同一条命令。这个单文件启动器会创建根 Context、挂载 Loader 插件,并让它从当前目录加载 ./cordis.yml;--import tsx 让 Node 不必构建就能直接跑 TypeScript:
node --import tsx ../../vendor/cordis/bin.js
预期输出是一行 hello from my first plugin,没有内容继续运行时进程会自己退出。注意插件文件里没有任何框架启动代码 —— 插件只描述自己的贡献,YAML 负责组合应用。
热重载、id,和那个静默的 PENDING
第 6 章把组合、热重载与排错串了起来。配置项除了 name 和 config,还能带别的元数据:id 给条目一个稳定标识,让 loader 能区分「改了现有条目」和「先删再加」;disabled: true 会卸载插件但保留条目,改回来时插件以及所有因等它而处于 PENDING 的插件都会重新加载。
热重载的配置长这样。hmr 要靠 logger 服务记日志,所以必须把 logger-console 一起挂上;它还会 inject timer 服务来做去抖:
- id: logger
name: '@deepseek-ai/cordis-plugin-logger-console'
- id: timer
name: '@deepseek-ai/cordis-plugin-timer'
- id: hmr
name: '@deepseek-ai/cordis-plugin-hmr'
config:
root: ['.']
- id: hello
name: './hello.ts'
改一下 hello.ts 里的日志再保存,输出变成先 reload、再打新日志:
hello from my first plugin 2026-07-22 15:44:36 [I] hmr watching [ '.' ] 2026-07-22 15:44:39 [I] hmr reload plugin at hello.ts hello from my EDITED plugin
但这里藏着第一个坑:不带 id 的配置项,每次读取都会拿到一个新生成的 id。所以只要配置文件被编辑过,即使那一条的文本完全没变,也会被当成「先删除再添加」重新挂载一遍。
第二个坑更隐蔽:inject 了没人提供的服务时,插件就一直等,不报错、不输出。官方明确说 PENDING 是合法状态,因为提供方可能稍后才挂载 —— 但代价是这个失败完全静默。官方给的诊断办法是直接枚举插件注册表去看 fiber 状态:
import { FiberState, type Context } from '@deepseek-ai/cordis'
export const name = 'diagnose'
export function apply(ctx: Context) {
setTimeout(() => {
for (const runtime of ctx.registry.values()) {
for (const fiber of runtime.fibers) {
if (fiber.state === FiberState.PENDING) {
console.log(`${fiber.name} is PENDING — a required service is missing`)
}
}
}
}, 500)
}
第三个坑直接写在教程正文里:如果配置项的模块无法被解析,比如路径或包名拼错,cordis 只会通过 logger 服务报个错,不会让进程崩掉;而在启动阶段,这条报告可能在 console 导出器开始观察之前就已经丢了。表现出来就是「新增了一条配置,但什么也没发生」。官方的建议是:先检查拼写。
我的判断
适合谁:正在自建插件式运行时或 agent harness 的团队 —— 它把「注册必须可逆」「依赖决定加载顺序」「事件分发模式固定」这三件事做成了框架级约束,而不是文档里的建议;也适合想看看「把设计理论单独写成论文」的开源实现长什么样的人。
不适合谁:想找一个装上就能用的成品框架的人。API 官方自述不稳定、版本还在 rc,上手路径是先 clone 另一个仓库跑 vendor 启动器,文档自述仍在建设中。
坑清单,全部来自原文实测:
- ▪根 README 25 字节,而 packages/create/README.md 全文只有一行 # create-cordis —— 很容易被误判成空仓库
- ▪rc 版本在 GitHub 上没勾 prerelease,prerelease 返回 false
- ▪不带 id 的条目,只要配置文件被编辑就会被重新挂载
- ▪inject 的服务没人提供时,插件停在 PENDING 且完全静默
- ▪模块解析失败只在 logger 里说一句,启动阶段还可能被丢掉
- ▪包名存在分裂:仓库内的包名是 @cordisjs/*,官方教程里 import 的是 @deepseek-ai/cordis,照抄不同来源的文档会装错
- ▪8,353 star 对应 35 个 watcher,star 与订阅比约 0.4%,热度主要来自榜单曝光而非日常跟进
数据来源:GitHub Trending 月榜(同期快照 8,365 star,本月 +8,270)· GitHub REST API 实测 8,353 star · 519 fork · 44 未关闭 issue · 35 watcher
仓库:https://github.com/cordiverse/cordis (TypeScript · 创建于 2022-05-17 · 最近推送 2026-09-08)
许可:MIT(LICENSE 为标准全文,Copyright (c) 2021-present Shigma,无追加自定义条款)
最新版本:v4.0.0-rc.10,2026-09-08 发布;GitHub 上 prerelease 标记为 false
论文:A Programming Paradigm for Spatiotemporal Composability,arXiv 2608.25512;论文仓库 https://github.com/cordiverse/paper (2,979 star,未开 issue 与 PR)
官方文档:https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer (原文自述 official documentation is still under construction)
抓取时间:2026-09-12;star 数以 REST API 为准,Trending 页快照略高,两者一并列出
本期到这里。如果你正在自建插件运行时,或者只是想看看「把设计理论单独写成论文」的开源项目长什么样,cordis 值得花一个晚上读。也欢迎留言告诉我:你更在意「注册必须可逆」,还是「依赖自动编排加载顺序」?
下期预告:modular/modular(29,685 star,本月 +3,080,Mojo/MAX);备选 NVIDIA-NeMo/Switchyard(2,876 star,本月 +2,628,Python)—— 在模型与供应商之间路由流量,同时保持 OpenAI 与 Anthropic 原生 API 兼容。
#开源框架#TypeScript#GitHub趋势