01-整体观 — Cordis 项目全貌解读
阅读对象:
e:\code\deepseek-harness\cordis(Cordis v4,cordis@4.0.0-rc.8)
方法:三观递进之第一观「整体观」——先看见,再看懂,最后看透。本文负责「看见」:为读者建立一张完整的项目认知地图。
系列:01 整体观(本篇)/ 02 具体观 / 03 深刻观,三篇层层递进。

总述:不是又一个 DI 容器,而是一门组合的"元"范式
Cordis v4 是一个可组合性元框架(Meta-Framework of Spatiotemporal Composability)。它不是一个业务框架,也不绑定任何具体领域;它回答的是一个更底层的问题:当软件由大量组件在运行时动态地装载、替换、卸载时,如何保证体系不会崩坏?
传统上,插件系统对这个问题只有"尽力而为"的答案:注册时给回调,卸载时凭自觉清理,依赖靠文档约定加载顺序。Cordis 把这两件事从工程纪律升级为运行时语义承诺:任何组件装载所产生的副作用,都可以被完整回滚(时间可组合性);任何组件对别的组件的依赖,都可以被声明并自动响应(空间可组合性)。正如其配套论文《A Programming Paradigm for Spatiotemporal Composability》所阐述的,这两个维度分别由"可逆效果"(revertible effects)与"响应式余效果"(reactive coeffects)两种机制承载,并统一进一个核心概念——Context(上下文)。
这套设计并非纸上谈兵:Cordis v4 是 Koishi 生态(@cordisjs 系列包)的下一代底座,同时被 @deepseek-ai/deepseek-harness(自进化 agent 的编排框架)以 vendor 形式整体引入作为底层插件框架。本仓库配套论文的动机场景,正是"self-evolving agent harnesses"——运行时不断修改自身的软件。
这份文档将按"项目定位 → 代码结构 → 设计理念 → 实现原理 → 整体流程 → 使用指南"的路径展开,并用五张图建立视觉化的认知坐标。读完本文,你应该能回答三个问题:它是什么?它由什么组成?它如何运转?
一分 · 项目定位与生态方位
1.1 它是什么:Koishi 生态底座的一次范式重写
Cordis 的名字在 Koishi 生态中历史悠久——它是 Koishi 长期使用的插件容器。v4 版本对这套机制做了彻底重写,从"Koishi 内部的插件库"升级为独立的元框架:packages/core/README.md 首行即定义 “A Meta-Framework of Spatiotemporal Composability”,并附上论文链接与文档链接;packages/core/package.json 将其描述为 “Meta-Framework for Modern Applications”。
配套论文《A Programming Paradigm for Spatiotemporal Composability》收录于同一组织的仓库 cordiverse/paper(preprint,Draft of August 13, 2026,官方注明 under active revision,引用时需标注版本)。论文摘要明确指出两块历史空白:现代软件(从插件系统到自进化 agent harness)越来越依赖动态组合,但其形式化基础却长期缺失。Cordis 的贡献在于把两个正交维度形式化并实现:
| 维度 | 问题 | 论文机制 | Cordis 实现 |
|---|---|---|---|
| 时间可组合性 Temporal Composability | 组件被移除时,其副作用必须被完全回滚 | 可逆效果(revertible effects):每个上下文变换携带逆变换,由运行时追踪 | Fiber 效果系统、ctx.effect()、DisposableList 逆序清理 |
| 空间可组合性 Spatial Composability | 组件间依赖需要被声明并响应式管理 | 响应式余效果(reactive coeffects):上下文变化按规格通知组件 | ReflectService 服务注册表、inject 声明、provide/notify 广播 |
| 统一 | 读写两个世界合二为一 | 单一上下文类型(context type) | Context 代理对象 |
图 1-1 · C4 Context 生态定位图:本图把一个典型 Cordis 应用放在三层坐标中——最上层是需要动态组合的应用(Koishi 生态、DeepSeek Harness),中间是 Cordis 元框架本身(core 内核 + loader/include/hmr 装配层),底层是 Node.js ESM 运行时。看懂这张图,就知道 Cordis"夹在应用与运行时之间"的元框架位置。
1.2 它在解决什么问题:组合的两难
任何鼓吹"插件化"的软件都会遇到两堵墙:
第一堵墙是时间之墙(回滚难)。装一个插件很容易——注册事件、创建服务、开定时器;但卸掉一个插件很难——要先撤销它做的每一件事,而开发者常常忘记、或者撤销顺序出错。Cordis 的答案是:把"卸载即回滚"变成运行时的结构性义务,而不是开发者的自觉。所有注册都通过 ctx.effect() / ctx.on() 等入口进行,它们自动登记进当前 Fiber 的逆序清理链。移除组件 = 按逆序执行一串 disposer,与 Git 的 revert 有异曲同工之妙。
第二堵墙是空间之墙(依赖难)。插件 A 需要插件 B 的服务,传统做法是硬 import、或者在启动清单里排好顺序。一旦体系变大,顺序编排会变成一场灾难。Cordis 的答案是:依赖用 inject 字段声明,服务用 provide 注册到统一仓库,谁先就绪、谁后启动,由运行时用通知机制动态调和——加载顺序从"脚本"变成"求解"。
这两堵墙的拆除,形成了 Cordis 全部代码的形式因。接下来我们从代码里验证这件事。
二分 · 代码结构与模块职责
2.1 Monorepo 拓扑:九个包,一个焦点
仓库采用 yarn 4 workspaces + yakumo 构建链(esbuild 打包 + tsc 类型 + vitest 测试 + eslint 校验),packages/* 下共 9 个包,全部 ESM:
| 包 | 类型 | 一句话职责 |
|---|---|---|
@cordisjs/core(cordis) |
运行时内核 | 唯一的"真理之源":Context / Fiber / Reflect / Events / Logger 五个核心对象 |
@cordisjs/plugin-loader |
装配层 | 声明式加载:Entry 树建模、配置调和、Node ModuleLoader 对接、isolate 隔离域 |
@cordisjs/plugin-include |
装配层 | cordis.yml 配置文件的双向读写(!!js 表达式、patch 补丁、原子写) |
@cordisjs/plugin-hmr |
装配层 | 热模块替换:依赖图分析、缓存清理、失败回滚 |
@cordisjs/plugin-timer |
工具插件 | effect 化的 timeout / interval / throttle / debounce |
@cordisjs/plugin-group |
工具插件 | 声明式 EntryGroup:一组配置条目作为一个可整体装卸的单元 |
@cordisjs/plugin-logger-console |
工具插件 | 将 LoggerService 的日志消息导出到控制台的 Exporter |
@cordisjs/utils |
工具库 | 与框架紧耦合但非运行时必需的小工具 |
create-cordis |
脚手架 | create-cordis CLI:从 npm 拉模板生成一个 Cordis 应用 |
注意依赖的单向性:core 不依赖任何 @cordisjs/plugin-*(它们只是 optional peerDependencies,用于类型层面的注入声明);loader 依赖 core;include 与 hmr 又依赖 loader。也就是说,无论外围装多少层,内核永远只有 core 一个包,业务逻辑永远长在外围插件里——这本身就是"元框架"的第一条纪律。
图 1-2 · 包依赖与职责分层图:Cordis 把"组合"拆成一条装配流水线——core 提供原语,loader 把配置声明解释成 Entry 树,include 让配置文件成为一等公民(可读可写),hmr 让源码热替换成为开发体验。三者都只是"内核之上的插件",印证了元框架的自我克制。
2.2 内核的九个源文件:一台精密机器的零件
packages/core/src/ 下只有 9 个 TypeScript 文件(8 个实现 + 1 个统一导出),这是 Cordis 的全部"物理存在":
| 文件 | 核心对象 | 职责一句话 |
|---|---|---|
context.ts |
Context |
唯一入口对象:由 ReflectService.handler 代理包装,提供 root / extend / isolate / intercept |
fiber.ts |
Fiber |
组件的生命周期单元:六态状态机、epoch 依赖指纹、惯性锁、ctx.effect() 可逆效果登记与逆序回滚 |
reflect.ts |
ReflectService |
服务仓库:provide / set / get / notify / accessor / mixin,是 ctx 代理 handler 与 createTraceable 的来源 |
registry.ts |
RegistryService |
插件注册表:ctx.plugin() 门口、Runtime 缓存、@Inject 装饰器与依赖解析 |
events.ts |
EventsService |
事件总线:emit / parallel / serial / bail / waterfall 五种分发模式 + internal/* 内部事件族 |
logger.ts |
LoggerService |
可调用服务范式(callable service)的样板:哈希配色、格式串、Exporter 管道 |
service.ts |
Service |
所有服务的基类:provide 自动注册、@Service.init / check / config 静态协议、代理安全的 instanceof |
utils.ts |
若干工具 | DisposableList、createTraceable、composeError 长栈、createCallable、各类 symbol 定义 |
index.ts |
导出 | 统一 re-export,拼装成 cordis 包的公共 API |
要点:内核几乎不含业务代码——它只定义"对象之间的关系",这正是"元框架"的含义。下一节将揭示这些对象之间的组合模型。
2.3 外围三包:声明式装配的完整闭环
- loader 定义"声明 → 运行"的映射:
EntryTree(配置树)→Entry(单个条目)→EntryGroup(条目组)→ctx.plugin()实例化出Fiber。它还负责把 Node 内部ModuleLoader(分 Node 22/23 的 v1 与 Node 24 的 v2 两个版本)接入,使得"import 一个插件模块"可以被追踪与清理。 - include 让配置文件具备两种身份:既是输入(启动时读入装配),也是输出(插件修改配置时回写文件,原子写保证安全)。
- hmr 站在二者之上:监听文件变化 → 分析 ESM 依赖图 → 分类 accepted/declined → 清理双缓存(ESM loadCache + CJS
require.cache)→ 重载相关插件,失败则整体回滚。
三分 · 设计理念与架构模式
3.1 三大核心理念
理念一:一切皆插件。 没有"框架内置功能"与"第三方插件"的硬界线:loader、include、hmr、timer、group 全部以插件身份装入(loader 自己就是 Loader extends EntryTree 的服务插件;连 ctx.plugin(isolate) 都被 loader 用于装配隔离域)。框架与扩展只有"内核原语"与"普通插件"的分层,没有特权。
理念二:一切皆服务。 服务是组件的对外接口:一个服务以稳定的键(如 ctx.timer、ctx.loader)挂在 Context 上,其他插件按键而不是按具体实现来找它。依赖倒置在这里被贯彻到极致——插件之间从不互相 import,只通过 ctx.<key> 与 inject 声明相遇。
理念三:注册皆可逆。 任何跨时间的资源占用都必须"成对出现":ctx.effect(() => { /* 安装 */ return () => { /* 卸载 */ } })。事件监听、服务提供、定时器、文件 watcher……全部收敛到这一条纪律上。这是 Cordis 全部可组合性的一号定理。
图 1-3 · Context → Fiber → Plugin 组合运行模型:图中看到 Cordis 的"三体"关系——Context 是舞台(服务仓库 + 事件总线),Fiber 是演员(插件的生命周期化身),Plugin 是剧本(函数签名 / Service 类 / apply 对象)。一个插件被多次装载,就有多个 Fiber;而 Fiber 的生死完全由 epoch 依赖图驱动。
3.2 架构模式识别
Cordis 不是发明新花样,而是把四种经典模式"可逆化"地焊在一起:
- Service Locator(服务定位器)+ IoC:经典 DI 容器的问题在于——服务一旦注册便成为全局事实,卸载无从谈起。Cordis 保留了"按名查找"的形态,但将注册动作本身放进 Fiber 的效果链,使服务的安装与卸载都是可逆事务(详见具体观第 2 节对
provide的解剖)。 - Proxy 拦截器:
Context的属性访问全部经由ReflectService.handler这个 Proxy handler 解释——ctx.xxx的每一次读操作都是一次"运行时路由"。这把「依赖查找失败、isolate 越界」等错误从"悄悄 undefined"变成"掷地有声的异常"。 - 事件总线(内部事件族):
events.ts内置internal/plugin、internal/status、internal/service、internal/update、internal/get、internal/set、internal/listener、internal/dispatch八个内部事件。框架自身就是事件驱动的:loader 拦截internal/update回写配置,reflect 通过internal/service通知服务变化——内部总线与业务事件共用一套分发机制,真正做到"知行合一"。 - 状态机驱动:Fiber 从诞生到销毁严格走在
PENDING → LOADING → ACTIVE → UNLOADING的轨道上(另有FAILED / DISPOSED两个终态),并由"时刻变化的 epoch 指纹"裁决何时迁移(详见 4.2)。
3.3 一句话总结本节的架构观
Cordis 的架构可以概括为:一个可代理的 Context(所有读取皆路由)、一组可回滚的 Fiber(所有写入皆事务)、一张可广播的依赖图(所有变化皆通知)。三者叠加,构成了动态组合的最小完备集。
四分 · 实现原理概述
本节只做鸟瞰,算法级别的细节留给《具体观》。这里的目标是让你带着"它们大概怎么工作"的直觉继续前进。
4.1 Context 是"宇宙":唯一可变量
Context 是整个系统的唯一"坐标系原点"。new Context() 会创建 ReflectService、RegistryService、EventsService、LoggerService 四件套,并把返回的 Proxy(而非原始对象)作为对外身份。之后所有扩展——ctx.extend()、ctx.isolate()、ctx.intercept()——都只是在这个坐标系上切出子空间。读取 ctx.xxx 时,Proxy handler 按"特殊属性 → 服务访问 → 内部 get 事件 → fiber.store 沿父链冒泡"的顺序解析;任何未声明的读取都会抛错,而不是静默返回 undefined。"找不到"是一种一等错误,这让配置错误在启动期就显形。
4.2 Fiber 是"生命周期":一个插件实例的时空胶囊
每次 ctx.plugin(plugin, config) 都会诞生一个 Fiber。Fiber 的核心职责有三:
- 承载效果:插件回调体内所有
ctx.effect()注册的 disposer,都被收进该 Fiber 的_disposables(DisposableList),卸载时逆序执行(先装后拆,天然满足依赖的栈式语义)。 - 计算依赖:Fiber 读取其
inject声明中的每个服务,把服务提供者的uid拼进一个 epoch 字符串——epoch 就是 Fiber 的"依赖指纹"。任何被依赖的服务卸载/重建,epoch 都会变化,触发该 Fiber 的_refresh()。 - 接受惯性:当依赖频繁抖动(如 HMR 期间连续重载),
this.inertia锁保证"卸载—重载"的循环不会并发踩踏,且无论中间抖多少次,最终收敛到一致状态。
图 1-4 · Fiber 六态状态机:这张图是 Cordis 的"心跳图"。
_setEpoch是唯一的换挡器:epoch 激活则_reload(LOADING → ACTIVE/FAILED),epoch 失效则_unload(UNLOADING → 重载或归位 PENDING),而dispose()让uid置空进入终态 DISPOSED。惯性与 epoch 的配合保证:状态无论怎么抖,最终只会停留在 ACTIVE 或 PENDING。
4.3 服务依赖 = 响应式广播
ctx.reflect.provide('timer', timer) 做两件事:把实现按"isolate 键"存入全局 store,并在当前 Fiber 的效果链上登记"卸载时删除"。任何 inject: ['timer'] 的 Fiber 都会在 notify 时被 _checkImpl 检查:服务是否已就绪?若就绪,_refresh() 重算 epoch 并重载依赖方;若未就绪,依赖方保持 PENDING 等待。这就是"空间可组合性"的运行时形态:inject 是空间上的声量,notify 是空间上的回响。
4.4 配置驱动装配:声明 → 运行
在 cordis.yml 里写下的每个条目(- id: xxx, name: '@scope/pkg'),由 include 解析为 EntryOptions,loader 将其组织成 Entry 树,最后每个 Entry 调 registry.plugin() 生成对应 Fiber。配置树与运行树一一对应:改配置 = 改声明;运行树会自动 diff 并执行最小化的装载/卸载。这一步把"程序的启动脚本"变成了"可被增量计算的声明"。
五分 · 整体流程
5.1 端到端启动链路
以 packages/core/bin.js(cordis 包自带的 CLI 入口)为例,完整链路只有三步,却走完了整个框架:
const ctx = new Context()
ctx.baseUrl = pathToFileURL(process.cwd()).href + '/'
await ctx.plugin(Loader) // ① 装入 loader 插件
await ctx.loader.create({ // ② 声明 include 插件读 cordis.yml
name: '@cordisjs/plugin-include',
config: { path: './cordis.yml' },
})
执行时发生的事情:new Context() 创建内核四件套 → ctx.plugin(Loader) 让 Loader 开始托管声明式加载 → loader.create(...) 把 include 作为 tree 根节点挂上 → include 读取 cordis.yml(js-yaml + !!js 自定义类型)→ root.update(data) 触发 EntryGroup.update 的 oldMap/newMap 差量调和 → 每个新条目 Entry._init() → import(name) 真加载插件模块 → ctx.registry.plugin(plugin, config) → 生成 Fiber → _reload() 执行插件回调 → ACTIVE。
图 1-5 · 启动时序与配置变更闭环:上半段展示"文件 → Entry → Fiber → ACTIVE"的装配时序;下半段的 loop 块展示运行期闭环——配置文件变化后,include 重新读取并驱动 diff 更新,插件 Fibers 随即被增量调整。启动是一条线,运行是一个环。
5.2 运行期的两个闭环
- 配置闭环(空间的响应):
internal/update是配置变更的统一入口。include 的文件变化、entry.update()的程序化修改、插件内ctx.fiber.update(config)的行为,都会经瀑布internal/update汇聚到对应 Entry,最终落到fiber.restart()——配置即事件,事件即配置。 - 生命周期闭环(时间的响应):服务
provide/卸载、HMR 重载都会触发notify→ 依赖方_refresh()→ epoch 变化 → 受影响 Fiber 走 UNLOADING → LOADING。任何一个字节的变化,都会在依赖图上荡开一圈涟漪,而这些涟漪全部是可逆的。
5.3 卸载回滚链
最高层的抽象是一条逆时间线:dispose() 置空 uid → emit internal/plugin → registry 移除 → _setEpoch(INACTIVE) → _unload() → 逆序执行全部 disposer(服务卸载、事件解绑、timer 清空,全部成对)→ 通知依赖方它们的世界已变。无怪乎论文把这一整套称为"revertible effects"——每一段副作用都带着自己的逆变换,时间在这里可以被倒放。
六分 · 使用指南与典型案例
实战向章节。DeepSeek Harness 项目(parent directory 中的
@deepseek-ai/deepseek-harness)已将 Cordis vendor 化,其docs/cordis-primer.md与examples/*/cordis.yml是本框架真实生产用法的一手素材,下列指南与案例均以它们为蓝本。
6.1 五个核心概念速查
用 Cordis 写插件,你只需要理解五件事(对应 cordis-primer.md 的 “Cordis In Five Ideas”):
| # | 概念 | 一句话 | 对应 API |
|---|---|---|---|
| 1 | 插件(Plugin) | 一个有 apply(ctx, config) 的函数、Service 子类、或 { apply } 对象的普通模块 |
ctx.plugin(fn, config);@Inject 装饰器 |
| 2 | 上下文(Context) | 服务的仓库;服务以稳定键 ctx.<key> 挂载 |
new Context()、ctx.extend() |
| 3 | 声明依赖(inject) | 插件声明所需服务;运行时等待其就绪,代替手工排序 | inject: ['tools', 'llm'] |
| 4 | 类型化事件 | 服务用 TS 声明合并定义事件名与负载,按语义选分发模式 | ctx.emit / waterfall / parallel / serial |
| 5 | 可逆注册 | 一切注册都走 ctx.effect(),卸载时逆序回滚 |
ctx.effect(() => () => {}) |
6.2 事件分发模式指南
每种事件在声明时就要定下一种分发模式(dispatch mode),并且只能由对应方法派发。primer 给出四种,加上 Cordis 内置的短路模式 bail,共五种:
| 模式 | 方法 | 是否 await | 顺序 | 有返回值? | 典型用途 |
|---|---|---|---|---|---|
| 观察 | emit |
否 | 注册序 | 无 | 生命周期通知、日志 |
| 环绕中间件 | waterfall |
否 | 注册序 | 有(next() 传递) |
请求渲染、策略裁决 |
| 扇出 | parallel |
是 | 并行 | 无 | 可并发的通知 |
| 顺序裁决 | serial |
是 | 注册序 | 有 | 顺序管道,可短路 |
| 短路 | bail |
否 | 注册序 | 有(首个非假值即停) | 单决策事件 |
waterfall 语义精讲(primer 原文核心):ctx.waterfall 是"包裹式中间件"。监听器收到 (...args, next):调用 next() 把(可能被包裹的)结果交给下一个服务;不调用 next() 直接 return,就是短路。值经由 next() 的返回值向下游传播。协作型监听器通常改写共享的请求/决策对象后放行;prepend: true 仅当必须跑到普通注册之前时使用。规则:新事件的 @mode 声明要与派发调用点交叉校验,声明与调用不一致即视为公共契约破损。
6.3 配置编写指南
@cordisjs/plugin-include 把 !!js 解析为表达式节点,cordis.yml 因此是"活"的:
- 条目结构:
id(唯一标识,:分隔分层)、name(包名/模块名)、config(插件配置)、inject(覆盖依赖声明)、disabled(是否停用)、group(嵌套子组)。 !!js求值时机(primer 原文规则):条目的config在已声明的注入激活之后、针对该插件的上下文(ctx.serviceName)求值;disabled则在每次装载决策时、针对 loader 上下文求值;include 会保留嵌套行表达式直到目标激活。其余元数据保持字面量。- overlay 补丁(
insert):当环境决定"在既有装配之上追加"时使用- insert:语法,按id定位目标 group 后追加条目(见 6.5 案例)。
6.4 真实案例一:jsonrpc-agent 的扁平装配
deepseek-harness/examples/jsonrpc-agent/cordis.yml 是一个无人值守的 coding-agent 部署(stdout 预留给 JSON-RPC),16 个条目平铺在顶层列表:
- id: sdk-jsonrpc-server
name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
config:
maxTokensAsSuccess: !!js "process.env.DSH_MAX_TOKENS_AS_SUCCESS === undefined ? true : JSON.parse(process.env.DSH_MAX_TOKENS_AS_SUCCESS)"
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
thinking: enabled
reasoningEffort: max
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
timeoutMs: 60000
- id: agent-spine
name: '@deepseek-ai/dsh-agent-spine-demo'
...
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
- id: token-meter
name: '@deepseek-ai/dsh-token-meter'
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
config:
thresholdRatio: 0.8
retainRatio: 0.16
maxTokens: 8192
三个值得玩味的细节:
- 装配顺序不决定加载顺序。
lsdk-jsonrpc-server在前、tool-fs在后,但谁先 ACTIVE 由inject依赖图决定,而不是文件顺序——这正是"声明式替代命令式排序"的现场演示。 !!js环境插值。cwd: !!js process.env.DSH_CWD ?? process.cwd()在插件上下文求值,使得同一份配置可以部署到不同机器而无需改动 YAML;compression行则用DSH_SNAPSHOT是否设置来切换 zstd/明文帧——配置文件的语义是启动时刻的,不是编辑时刻的。- 无配置条目依然有意义。
subprocess、token-meter只写了id/name,表示"用默认配置装载",是配置冗余度控制的范例。
6.5 真实案例二:web-schedule 的 overlay 补丁
web-schedule/cordis.yml 只有 8 行,却演示了 Cordis 最优雅的用法——在既有装配上叠一层:
# Opt-in Schedule patch over the shipped Web composition. The owner observes
# only roots published after this overlay loads.
- insert:
- id: time-context
name: '@deepseek-ai/dsh-time-context'
- id: schedule
name: '@deepseek-ai/dsh-schedule'
- insert: 告诉 include:把下面的条目插入到现有配置树的根 group(top-level list)之后,而不是替换它。于是"web 组合 + 时间上下文 + 调度"成为一棵树,而补丁文件本身可以独立开关、独立版本化——组合是增量叠加出来的。insert 的实现要点:id 定位目标 group,同名冲突需 name 校验,config 等覆盖字段按 overrides 规则合并(详见具体观第 6 节)。
6.6 实践规则三条(primer “Practical Rules” 提炼)
- 按领域封装插件:工具管道事件归
ctx.tools,模型流式归ctx.llm,agent 协同归ctx.agents——服务键就是领域边界。 - 事件 vs 服务方法选型:拦截与策略用事件(可插拔、可短路);直接能力调用用服务方法(可靠、可 await)。不要用事件做"必然会执行一次"的调用。
- disposer 纪律:每个注册都要有 disposer——要么在
ctx.effect()里返回它,要么用 Cordis 自带 helper(ctx.on()、ctx.timeout()等已内置)。如果拆卸顺序重要,把相关操作放进同一个 effect,让回滚按意图顺序展开。
总述 · 收束:一张认知地图
把全文合起来,Cordis 的整体画像是一张四象限的地图:Context 是坐标系(一个可代理、可扩展、可隔离的宇宙,所有读取皆经路由);Fiber 是时间(每个组件的一生是一条可逆的时间线,epoch 是快进/回放的档位);Reflect 是空间(服务仓库上空的依赖图,notify 是每一次空间地震的余波);Loader/Include 是地图本身(cordis.yml 的声明树与运行树一一对映,改图即改世界)。四个部件都不大——内核 9 个文件——但它们凑在一起,就让"动态组合"从工程技巧升格成了一个有论文、有演算、有实现的范式。
现在你已经"看见"了 Cordis。下一篇《具体观》将潜入水下,逐一切开这四件套的算法与实现——每刀都配上论文与代码。
下一篇预告:可逆效果系统(
_execute的四种归一化)、响应式余效应(provide/notify双通道)、Proxy 语义(createTraceable)、五种事件分发、长栈工程(composeError)、配置调和(group diff)、热替换(accepted/declined 不动点)。
更多推荐



所有评论(0)