Cordis:5.4k stars、今日 +959 的 "时空可组合性" 元框架,正在悄悄支撑 DeepSeek Harness 的 Agent 运行时
有一类开源项目,作者的代码量很克制,文档却厚得像论文;他们不发 Twitter,不在 Discord 里” 营销”,GitHub Trending 上也从来不是那种” 一次最多待两小时就掉榜” 的流星。这种项目往往是某个被悄悄复用的” 基础设施级” 东西 —— 你日常用的一个大玩具底下,可能就垫着它。
上周我就撞到了一起。
我本来在研究 DeepSeek 前一阵开源的 DeepSeek Harness(DSH)—— 一个跟 LangGraph / CrewAI 同一梯队的 AI Agent 编排框架,按理说它的 plugin 体系应该自己写一套嘛,对吧?结果翻到 DSH 的 vendor 目录,第一条注释就把我逗笑了:
“Cordis is vendored in DeepSeek Harness as the underlying plugin framework.”
DSH 不是” 用了” Cordis—— 它是把 @deepseek-ai/cordis 4.0.1 整包 vendor 进仓库里。换句话说,DeepSeek 在做自己那一整套 Agent 编排哲学的时候,选择不重新发明插件系统,而是把一个名字你可能没听过、社区讨论不多、只在 paper 一栏挂了一篇 PDF 的 Node.js 框架,请到工程的心脏位置。
这个框架就是 cordiverse/cordis—— 一款自称 “A Meta-Framework of Spatiotemporal Composability”(时空可组合性的元框架)的 TypeScript 开源项目。它今天已经爬到 GitHub Trending 的 #9,5,378 stars / 290 forks,但更炸的数字是今日 +959 stars—— 对一个 4 年历史的 repo 来说,这种日增量是反常的。
我花了周末把它的 paper(2026 年 8 月 13 日的最新草稿)、cordis-primer、核心包代码、最近一周的 PR 全过了一遍。下面是我最关心的那几个问题,以及我给出的答案。
一、为什么又一款” 插件框架” 能上 Trending—— 而且日增近千
先把” 为什么这件事重要” 说清楚。
我自己写过的、或者读过的 Node.js 插件框架,少说也有十几款了:NestJS 的 Module、Vue 的 Plugin、Rollup 的 Plugin、Fastify 的 Plugin、vscode 的 extension host、Electron 的 IPC、Koishi 的 plugin,甚至 Cordova 的 hook…… 每款都说自己是” 插件系统的最优解”。
但如果你把” 插件系统” 这个词拆开看,每家其实只回答了其中一部分问题:
- NestJS / Vue 答了” 组件怎么挂到根上下文”—— 但完全没有” 插件卸载的时候副作用能不能干净地回滚”。
- Rollup / Webpack 答了” 如何按声明的依赖驱动加载顺序”—— 但你问它” 插件之间能不能互相 override 配置、有冲突时回退到上一次的 committed 值”,它就沉默了。
- VSCode extension host 答了” 插件之间靠 extension API 通信,进程隔离”—— 但它是用进程隔离换隔离,代价是 IPC,而且 Undo 仍然 owner 在插件自己手里。
- LangGraph / CrewAI / AutoGen 这种 2024 年以后火起来的 AI Agent 编排层,看上去” 很动态”—— 但它们在底层仍然要落一个插件系统,最后要么自己写一个迷你版(LangGraph 的 Pregel),要么干脆把 LangChain 那套 Retryable / Runnable 的迁移语义直接当配置层用。
真正难的问题是这样的:
当你装一个 AI Agent 框架的时候,你装进去的不只是一个对象 —— 你装进去的是一个可能 30 秒后会重写自己上下文的、有副作用的、可能还会在执行到一半时被要求撤回的活物。
想清楚这件事,你才能体会 Cordis 想要解决的” 两个正交维度”:
- 时间可组合性(Temporal Composability):组件卸载时,它的副作用能不能完整地、可预测地回滚?
- 空间可组合性(Spatial Composability):组件之间互相声明依赖,加载顺序由依赖关系自动推导,而不是手工编排启动序列?
Cordis 把这两个维度形式化成 effect(效应) 与 coeffect(余效应) 两套 runtime 机制。这一点,跟经典的 algebraic effects /coeffects 一脉相承,但项目方把它” 焊接” 到了一个具体的、能跑、能 hot reload、能 inspect 的 TypeScript 框架里。
paper 摘要里有句话特别点睛:
“Modern software—from plugin systems to self-evolving agent harnesses—increasingly requires dynamic composition, yet its formal foundations remain underdeveloped.”
self-evolving agent harnesses. 我很久没在 paper 里读到一个这么有野心的词组了。
二、五个核心概念:Cordis 的全部世界观
cordis-primer(也就是 DSH 给 harness 插件作者写的入门手册)一开始就把整套系统压成了五个核心概念。我把它原样翻译,再补一些我的解读。
1. 插件 = 实现 Service 的对象
1 | import { Context, Service } from '@cordis/core' |
一个插件要么是个带 inject 和 apply(ctx) 的函数,要么是上面这种 Service 子类。它的整个生命周期由 Cordis 挂到当前 context 上:实例化靠 inject 等待依赖、活跃期间可以通过 ctx.foo 拿其他服务、被卸载时所有 ctx.effect 注册的内容自动反转。
这跟 NestJS 的 Provider 看上去很像,但关键差异在第 5 点。
2. Context = 服务的容器,每个服务占据一个稳定的 ctx.<key>
1 | const ctx = new Context() |
注意是 ctx.hello 而不是 Hello.say(...)。整个框架里你几乎不会”import 实现类”—— 所有跨插件通信都通过 ctx.<key> 拿服务。这就把” 插件之间是平等的、互相不知道实现细节” 这件事做死了,是后面 effect 反转能干净的基础。
3. inject = 声明式依赖,加载顺序由依赖推导
1 | static inject = ['http', 'database'] |
写完上面这一行,Cordis 会自动等 ctx.http 和 ctx.database 都 ready,再启动你的插件。需要多轮依赖也没问题,依赖图是 DAG 的话就一定能解出来。
这对于写 Agent harness 的人来说极友好:你写个 LLM 调用插件,只关心 ctx.llm、ctx.tools、ctx.sessions 在不在;至于谁先谁后,由 harness 编排层决定。
4. 类型化事件分发(emit /waterfall/parallel /serial)
这是 Cordis 我觉得最值得吹的设计。
它把事件分发模式显式做成四种,每种只能用自己的方法触发:
| 模式 | 是否 await | 顺序 | 返回值 |
|---|---|---|---|
emit |
否 | 注册顺序 | 无 |
waterfall |
否 | 注册顺序 | 有(每个监听器可包 / 可短路) |
parallel |
是 | 并行 | 无 |
serial |
是 | 注册顺序 | 有 |
注意一个非常硬核的细节:事件分发模式是事件公开约定的一部分。也就是说你定义事件时就要声明它是 emit 还是 waterfall,然后这个声明会被 harness 生成的目录跟实际分发调用点做交叉校验 —— 一个 typecheck 帮你找出” 我以为是 waterfall 但被当 emit 用了” 的 bug。
waterfall 的语义最像 Koa / Express 的中间件:
1 | ctx.on('strategy/select', async (req, next) => { |
策略类插件如果不调用 next(),就拿到了” 最终决策权”,相当于短路返回;如果是观察类插件,不调用 next() 是错的 —— 你必须委托。
5. 注册 = 可逆的副作用
这一条才是 Cordis 跟” 另一个 NestJS” 的关键差异。
所有” 会留下痕迹” 的操作 —— 挂个 listener、注册个 tool schema、装一个 prompt 片段、初始化一条 WS 连接 —— 都通过 ctx.effect() 或者 ctx.on() 注册并返回一个 disposer:
1 | ctx.effect(() => { |
卸载插件 / 热重载 /harness roll back 时,Cordis 按注册相反顺序遍历 disposer,依次执行回滚。
这才是 AI Agent 框架真正想要的特性。
你想想看:一个 agent 在执行第 7 步的时候,用户说了” 算了,撤销”。那么从第 7 步到第 1 步之间注册的 timer、订阅的消息队列、申请到的临时文件、产生的中间状态 —— 能 100% 还原吗?传统插件框架里,这件事 100% 是写插件的人自己保证的(”please remember to clearInterval on dispose”)。Cordis 把” 可逆” 从道德劝说升级成了 runtime contract。
paper 把这个特性叫做 revertible effects:每个 context 变换都自带一个 inverse,runtime 帮你追踪。
三、技术架构:effect × coeffect 的” 形式化骨架”
光看五点概念还不够炸。我把 paper 和核心包交叉看了一遍之后,画一个简化版的架构图给感兴趣的读者。
3.1 两个 Context:Effect context + Coeffect context
传统插件系统只有一个 context(” 我有什么”),Cordis 把它劈成两个:
- Effect context:记录” 我这个插件对外造成了什么”—— 已注册的 listeners、已分配的 timer、已 effect 化的资源。Cordis 通过这些记录反推” 卸载时该怎么撤销”。
- Coeffect context:记录” 我这个插件依赖别人提供了什么”—— 通过
inject声明的服务集,加上ctx.on(...)的监听器对应的” 我希望上游给我什么”。
每个 context 变化,runtime 都会通知所有相关组件(component)。
这听起来抽象,换个 AI Agent 场景就具体了:
你写一个
prompt-rewrite插件,声明inject = ['llm', 'prompt-policy'],并ctx.on('prompt/build', ...)注册了一个监听器。
- coeffect 端:Cordis 看到 inject,发现你依赖
ctx.llm、ctx.prompt-policy,等它们 ready。- effect 端:Cordis 把你的
prompt/build监听器登记进分发器,生成对应的 disposer—— 一旦插件卸载,prompt/build上别人再 emit 的时候,你这个监听器不在了。
prompt-policy 后来改了一版,harness 触发 hot reload,Cordis 会沿着依赖图反推” 谁依赖了 prompt-policy → 谁要重 build → 谁注册的 prompt 要 recompose”,整个过程是用 effect/coeffect 机制推导出来的,不是手工编排。
3.2 一个统一的 Context type
paper 第二段提到,Cordis 把 effect 和 coeffect 统一到一个 context type 里。这件事读起来像废话,做起来却意味着:
- 一段代码不需要在” 我现在是 producer” 和” 我现在是 consumer” 之间反复横跳;
- type 系统天然就能告诉你:现在调用
ctx.effect(fn),fn 的返回值就是 disposer;现在写static inject = [...],声明的就是上游该给你什么。
这给了 Cordis 一个 Node.js 生态少见的东西:“插件系统” 作为 type-level 的契约,而不仅仅是一组函数调用约定。
3.3 Component 与动态组合演算(Calculus of dynamic composition)
“After that, we combine these mechanisms into the notion of a component and give a calculus of dynamic composition, whose metatheory carries spatiotemporal composability from a single component to a whole system of interleaved components.”
这句是 paper 里最不容易啃的一段,但核心意思其实可以一句话转述:
单组件的时空可组合性,可以被元理论 (metatheory) 推到整个系统的可组合性。
也就是说,paper 给出了一个数学化的保证:每个 plugin 自身可逆 + 每个 plugin 显式声明依赖 → 整个组合系统也是可逆、可推导的。
这种” 用形式化保证动态组件系统正确性” 的论文路线,函数式编程那一支比较熟(见 Eff 语言 / Koka 的 coeffect 论文),Cordis 是少有的把它工程化到一个 producer-grade runtime 的尝试。
3.4 实际代码骨架
不写代码就跑题了。Cordis 的最小完整例子:
1 | import { Context, Service } from '@cordis/core' |
如果你接下来 ctx.registry.delete(Reporter.service),所有 Reporter 注册的东西 ——report/build 监听器、database 依赖图中的边 —— 都会被 inverse 回滚。
四、谁在用 Cordis:DeepSeek Harness 是冰山一角
我以为 DSH 是孤例,于是顺手扫了一圈 issue 区。
Issue #76(2026-08-16 提交,作者 mook-wenyu)的标题非常直观:
“Subagent’s ask_user_question never surfaces to the parent agent — child keeps running until max-tokens (report from DSH, a cordis-based framework)”
内容翻译过来是:DSH 是基于 cordis fork 的应用,它的 Web GUI 里子 agent 调用 ask_user_question 这个工具,结果问题不会透到主 agent 或 UI,子 agent 会一直被 token 限制顶死。Issue 里把 DSH 内部 5 个包的具体代码位置 + 报错路径 + 修复期望全列了。
这种” 上游 issue 区里写应用层 bug report” 的现象很有意思 —— 因为 DSH 仓库自己关了 issues,所以团队把反馈 sheet 写到了 cordiverse/cordis 这边。这间接证明 Cordis 的 vendoring 不是一次性的” 塞个库进去”,而是 DSH 的整个 agent 模型都深深地靠着 cordis 的 dispose /inject 语义在工作。
另外几个间接的” 重度用户” 线索:
- 仓库的 contributors 最近一周里有
tdwhere123(来自 cordis loader 的复杂 PR)、randomcat4(另一位持续贡献者),以及更早的 OpenAI Codex 自动 PR(PR #75docs: add contribution guide由 GPT-5 代写,author disclosure 写了 Autonomous by OpenAI Codex)。 - Claude Code 本身作为 PR generator 在 §6.7 substrate 的 Wasm 路线实验中现身(issue #78,作者 inso1337 用 Claude Code 写了一整套 WAT / AssemblyScript / Python 组件化实验)。
- 上游 cordiverse 跟 deepseek-ai 的关系,从
@deepseek-ai/cordis这个 fork package name 一眼可见 —— 两个组织之间的依赖路径是”cordiverse 发布 core,deepseek-ai vendor 一份做修改,挂自己的 issue 追踪”。
这给我们一个非常关键的旁证:
一个严肃的 AI Agent 框架团队,没有自己造 plugin runtime,而是选择 fork 一款学术味道很重、正在快速迭代的 Node.js 元框架。这是” 开源界对 Cordis 投的现实票”。
五、快速上手:一个能在 5 分钟内跑完的例子
如果你想自己验证一下,不是花 5 分钟就能跑通的 ——Cordis 仍然处在 4.0.x 阶段,作者在 README 里写得很直白:
“Cordis is under active development. The API is not yet stable and may change without notice.”
这句话非常诚实。如果你打算把它接到生产 Agent harness 上,至少要做好两件事:锁版本 + 跟 upstream 同步 PR。
5.1 安装(Yarn workspace 模式)
Cordis 自己用 Yarn 4 + Corepack:
1 | corepack enable |
JS / TS 端的依赖入口:
1 | { |
5.2 用 Cordis 写一个最简单的 plugin 与一个 hot reload 实验
1 | // greeter.ts |
1 | // main.ts |
sub.update() 触发的就是 时空可组合性 ——effect context 把 greeting 的旧值 dispose、coeffect 解析重新跑一遍依赖、运行时切换到新 config。整个过程不需要重启进程、不需要重 import 模块。
这就是 Cordis 想给 AI Agent harness 的那种” 边跑边换零件” 的能力。
5.3 一些不那么友好的真相
实话说,如果你想” 立刻把 Cordis 接到生产”,目前会有几个痛点:
- API 不稳定:作者在 README 里白纸黑字写了”may change without notice”,我读代码也确认
Effect类型和Fiber状态机在最近 5 个 PR 里都还在大改。生产集成建议锁 patch 版本,等 minor 稳定了再升级。 - 学习曲线靠 paper 而不是文档:
cordis-primer写得简洁且数学,但internal/plugin、internal/update、internal/filter三层之间的差别,没有一个完整 tour 走完,你很难做出” 我要不要 fork 这个 loader” 的判断。 - Niche:生态不大,遇到坑大概率要直接看
packages/core/src/*.ts源码。Cordis 当前的” 出货” 是给你一个元框架 + 一组 helper,不是给你一个” 开箱即用” 的 Agent SDK。
但反过来 —— 这恰恰是 DeepSeek Harness 在做的事:自己 fork 一份 + 自己补上层 + 跟 upstream 一起演进。Cordis 的真实身份更像一个研究型基础设施,而不是一个” 普通开发者今天就拿来用的工具”。
六、总结:谁应该现在关注 Cordis
写到这里我已经想清楚了今天这篇博客的结论,应该分两段说。
给” 造 AI Agent 框架” 的人
Cordis 给你的是一套可形式化推理的 plugin 系统。如果你正在设计一个 agent harness,并且开始被以下问题困扰:
- 加载顺序复杂到需要写一堆插件优先级,
- HMR 重新装插件之后有副作用泄漏,
- 多 agent 上下文切换时工具注册 / 卸载不可控,
- 撤销 /rollback 流程需要每个插件自己维护 dispose,
那 Cordis 的 effect /coeffect 模型值得你花一两个周末读一遍 paper + 跑一遍 primer。哪怕最终你决定自己造 —— 你至少能清楚地知道自己要放弃什么。
给” 用 AI Agent 框架” 的人
如果你和我一样,平时更多是 LangGraph / CrewAI / DSH / OpenAI Agents SDK 的使用者,那 Cordis 今天的 959 日增 stars 至少说明一件事:
严肃团队正在认真地把” 动态可组合性” 这件事当成一等公民来设计。这件事最终会渗透到你日常用的 Agent 框架里,让 hot reload、rollback、tool registration 变得越来越自然。
你不用立刻学 Cordis 的 API。但你应该知道这个流派的存在,因为它会在接下来一两年决定 AI Agent 框架的能力上限。
最后照例贴上链接:
- GitHub 仓库:https://github.com/cordiverse/cordis
- 配套 paper:https://github.com/cordiverse/paper(2026-08-13 草稿)
- 入门手册 cordis-primer:https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer
- 依赖案例 DeepSeek Harness:https://github.com/deepseek-ai/deepseek-harness
- MIT 协议:可商用、可 fork、可 vendor,跟
@deepseek-ai/cordis一样。
今天就到这里。如果周末有空,我打算把 Cordis 的 effect 形式化部分再翻译一版 —— 重点对照 paper 的 §3~§4,把”revertible effects 的 monotone trace” 那段拆开讲讲。