为什么用 Cordis 做 AI Agent 运行时:从 QQ 机器人框架到 DeepSeek Harness
DeepSeek Harness 系列第六篇。Cordis 原本是 Koishi(QQ 机器人框架)的底层——一个国人开发的"小众"插件框架。DeepSeek 为什么选它来支撑 50+ 包的 Agent 运行时?
背景:Cordis 是什么
Cordis 是 cordiverse/cordis 项目,最初为 Koishi(一个 QQ/Discord/Telegram 机器人框架)设计的插件运行时。它的核心只有约 2000 行 TypeScript,提供:
- Service:命名的 ctx 键,任何插件可以提供/消费
- Fiber:插件的生命周期状态机(PENDING → LOADING → ACTIVE → DISPOSED)
- Effect:注册即 disposer 的副作用管理
- inject:声明式依赖等待
- Events:类型化的 emit/waterfall/serial/parallel 事件
Harness 将其 vendor 进仓库(vendor/cordis,版本 4.0.0-rc.7),rescope 为 @deepseek-ai/cordis,并追加了 18 项本地修改(生命周期加固、事务性配置加载、JSDoc 等)。
为什么不用"主流"方案
方案 A:InversifyJS / tsyringe / NestJS
传统 DI 容器做的事:
// InversifyJS 风格
@injectable()
class ToolRegistry {
constructor(@inject('LLM') private llm: LLMService) {}
}
container.bind('ToolRegistry').to(ToolRegistry)
const registry = container.get('ToolRegistry')
问题:
- 没有自动 dispose:你
bind了一个 service,谁来负责unbind+ cleanup?传统 DI 要么不管(leak),要么需要手动 lifecycle hook - 没有 dependency-driven reload:如果 LLM provider 热替换了,依赖它的 ToolRegistry 应该自动重启——传统 DI 做不到
- 配置和实例化分离:
cordis.yml一行就是一个 plugin 实例,改配置触发 HMR——传统 DI 的配置通常是启动时一次性的
Cordis 的关键差异:它不只是 IoC,它是一个有 lifecycle + reactivity 的 plugin orchestrator。
方案 B:自己写一个
50+ 包的 monorepo,如果从头设计:
- Plugin registry + lifecycle state machine
- Dependency graph + topological sort
- Hot reload protocol
- Effect tracking + ordered disposal
- Typed event system with waterfall semantics
- YAML config loader + patch/overlay composition
这大概是 6000-8000 行基础设施代码,加上持续的 edge case 修复。Cordis 把这些已经做了,而且在 Koishi 生态中经过了真实的多插件并发运行验证。
方案 C:不用 DI,直接 import
// 直接 import 方式
import { toolRegistry } from './tool-registry'
import { llmService } from './llm-service'
// 每个模块直接引用具体实现
这在 Agent 框架中是不可行的,因为 Harness 的核心卖点就是可替换性——用户必须能通过配置换掉任何一层。直接 import 把依赖硬编码到了编译时。
Cordis 解决了什么 Agent 特有问题
1. Service 消失时的 graceful degradation
AI Agent 运行时有一个独特场景:service 可以运行时消失。
- MCP Server 崩溃 →
ctx.tools中的 MCP tool 被注销 - LLM Provider 因为 rate limit 暂时不可用
- 文件系统 watcher 被操作系统杀死
传统 DI 的假设是:一旦 bind,service 就一直在。Cordis 的假设是:service 可以随时出现和消失。
export const inject = ['tools']
export function apply(ctx: Context) {
// 如果 ctx.tools 的 provider 被卸载(比如 HMR 替换):
// 1. 本插件自动进入 DISPOSED
// 2. 所有通过 ctx 注册的 effect 逆序执行
// 3. 当新的 tools provider 加载后,本插件自动重新 apply
}
这个行为是 Cordis 框架级保证的——每个使用 inject 的插件都自动具备这个能力,不需要手动写重连逻辑。
2. 声明式组合 + HMR
AI Agent 的开发循环要求极快的反馈:改了一个 tool 的 prompt,想立刻看效果。
Cordis 的 cordis.yml + HMR plugin 提供了这个能力:
- id: my-tool
name: './src/my-tool.ts'
config:
temperature: 0.7
修改 temperature → Cordis loader 检测变化 → 旧 fiber dispose(所有 effect 清理)→ 新 fiber 用新 config 加载。整个过程不重启进程,不丢失其他 session 状态。
传统 DI 框架没有"文件变化 → 局部热替换 → 依赖自动重建"这条路径。你要么重启整个进程,要么自己写一套 HMR 协议。
3. 配置层的 patch/overlay 组合
Harness 的 Profile + Bundle 系统:
base bundle → plugin bundles → profile patch → home patch → CLI overlay
每一层都是 Cordis 配置的一组 patch 操作。这个组合模型直接来自 Cordis loader 的 Include + patch 机制——Harness 只是定义了层的优先级规则。
如果自己实现,这个"多层配置合并 + 引用已安装 npm 包中的 patch 文件"的逻辑相当复杂。
4. Effect-scoped 资源管理
Agent 运行时有大量需要生命周期管理的资源:
- MCP 连接(要断线重连、要清理 tool 注册)
- PTY session(要维护状态、要在 Agent 结束时 kill)
- File watcher(要在 workspace 切换时重建)
- Background job(要在 session 结束时取消)
全都通过同一个模式管理:
ctx.effect(() => {
const resource = acquire()
return () => resource.release()
})
不需要为每种资源设计不同的 lifecycle hook。这比 NestJS 的 OnModuleInit / OnModuleDestroy 更通用——因为 effect 可以嵌套、可以条件性执行、可以在运行时动态添加。
Cordis Waterfall:around-middleware for events
Cordis 的 waterfall 事件是 Harness 架构中大量使用的模式:
// tools/pre-execute 是 waterfall 事件
ctx.waterfall('tools/pre-execute', async (toolName, args, next) => {
// 在 tool 执行前做权限检查
if (!hasPermission(toolName)) {
return { denied: true, reason: 'No permission' } // 短路,不调 next()
}
// 放行
return next() // 必须调用,否则后续 listener 不执行
})
这个模型和 Koa/Express 的 middleware 类似,但应用在事件系统中:
agent/pre-step:决定模型这一步看到什么agent/request:拦截或修改即将发出的 LLM 请求llm/stream:处理流式响应tools/pre-execute/tools/execute/tools/post-execute:工具执行管道
每个 waterfall 的 listener 可以选择:
- 调
next()放行(大多数情况) - 不调
next()短路(策略拒绝) - 调
next()但修改参数或包装返回值(around advice)
这比纯 emit/subscribe 强大得多——它允许多个独立插件组成一个决策链,而不需要知道彼此的存在。
Vendor 的代价和收益
代价
Harness 做了 18 项本地修改,维护成本不低:
- Fiber lifecycle hardening:修复了 3 个 reentrant disposal 的 race condition
- Transactional config reload:让配置变更失败时能回滚
- Lazy config resolution:
!!js表达式在 inject 激活后才求值 - Include patch semantics:让后一个 patch 能覆盖前一个 patch 插入的行
每次 upstream 更新,都要 re-apply 这些修改。
收益
- 完全可审计:vendor 在仓库里,不依赖外部注册表的版本
- 可 patch:遇到 upstream 不接受的修改,不需要 fork + 长期维护
- 版本锁定:不会被上游 breaking change 意外破坏
- 性能调优:可以针对 Harness 的使用模式优化热路径
为什么"小众"不是问题
Cordis 在 npm 上的下载量可能不如 InversifyJS,但:
- 代码量小:核心 ~2000 行,可以完整 review
- 语义明确:5 个核心概念(plugin, service, inject, effect, event),没有 magic
- TypeScript-first:完整的类型推导,declaration merging 让扩展点 type-safe
- 实战验证:Koishi 生态有上百个插件并发运行的经验
- DeepSeek 自己维护:vendor 后由 Harness 团队负责,不依赖外部 maintainer
对于一个 AI Agent 运行时来说,最重要的框架特性不是"生态大",而是:
- 插件可以安全地注册/注销资源 ✓
- Service 消失时依赖方自动重启 ✓
- 声明式配置组合 + 热替换 ✓
- Around-middleware 事件管道 ✓
Cordis 恰好把这四件事做好了,而"主流"DI 框架做的是另一组事情(HTTP 路由、请求生命周期、模块化)。
对 Agent 框架选型的启示
如果你也在构建 Agent 运行时,Cordis 的设计给出几个值得借鉴的方向:
- DI 不够,需要 lifecycle:传统 IoC 解决"谁创建谁"的问题,但 Agent 运行时需要"谁什么时候活着"的管理
- 配置即组合:Agent 的能力集应该是配置决定的,不是代码决定的
- Effect tracking 比 manual cleanup 可靠:资源泄露是 long-running Agent 的大敌
- Around-middleware 比 hook callback 强:决策链(permission → approval → timeout → execution)需要有序短路能力
参考链接
- Cordis 入门
- Cordis 框架教程
- vendor/README.md(manifest + 修改日志)
- cordiverse/cordis(上游仓库)
- Koishi(Cordis 最早的实战平台)
DeepSeek Harness 系列文章:
- 第六篇:为什么用 Cordis 做 AI Agent 运行时(本文)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)