DeepSeek Harness:插件化架构实现任意环节动态替换

在介绍过DeepSeek Harness的安装、插件使用以及桌面端和CLI工具之后,我们今天来拆解它的核心架构——Cordis,这也是这款AI代理框架最具创新性的部分。它的核心理念是“一切皆插件”,从模型适配器、工具注册表、会话日志到Agent运行循环,每一个模块都可以在运行时独立替换,更换模型服务商不需要修改源码,切换沙箱后端不需要重启,甚至替换Agent运行逻辑也无需改动框架本身。这种“任意环节可替换”的设计,是DeepSeek Harness和传统Agent框架最大的区别。
一、DeepSeek Harness的组装流程
整个DeepSeek Harness并不是一个固定封装的应用,而是通过多层配置叠加,在运行时动态组装出完整的插件组合。这套架构分为三个核心层级:
-
最底层是Cordis微内核:这是专门管理插件加载、卸载和协作的基础框架,最初由开发者Shigma为聊天机器人框架Koishi编写,2022年独立开源,之后被DeepSeek整合进项目,连驱动Agent运转的主循环本身也是Cordis插件。
-
中间层是三层组装机制:这套机制解决“整套插件组合如何搭建”的问题,包括:
- Profile:本地存储的配置方案,列出需要加载的插件组合,同时保存用户自定义的配置补丁
- Bundle:插件打包包,将一组关联插件和配套配置打包分发,安装一个Bundle即可一次性加载多个配套插件
- Patch:配置补丁,通过名称定位到指定插件,替换其原有配置
-
顶层是四种运行模式:Standard(全功能版)、Code(面向编程场景)、Minimal(仅保留Shell和文件编辑工具)、Creator(面向实验开发),四种模式对应不同的插件组合,切换模式不需要修改任何代码,只需更换插件集即可。
插件组合的加载顺序遵循“逐层覆盖”的规则:先安装基础Bundle,再应用Profile配置补丁,接着加载本地自定义补丁,最后还可以通过启动命令临时添加额外补丁,每一层的配置都可以覆盖上一层的设置。
二、插件系统的核心概念
要理解Cordis的插件系统,首先要明确几个核心概念,不需要死记名称,只需理解它们各自的作用:
-
插件编写方式:简单功能可以用函数实现,需要对外提供调用能力的用类封装,也可以直接使用对象,三种写法的加载和卸载流程完全一致。
-
Context:系统能力的容器,每个能力都有固定的命名空间,比如ctx.tools对应工具管理能力、ctx.llm对应大模型接口、ctx.sessions对应会话管理。其他插件只需要通过名称调用能力,不需要关心具体实现细节,这也是系统可以任意替换组件的基础。
-
Service:可被其他插件调用的能力封装,用类编写的Service会自动完成注册和注销流程,插件卸载时会自动移除对应的服务。
-
Fiber:每个插件的生命周期记录,插件从创建到销毁会经历排队等待依赖、启动中、运行中、卸载中、已销毁五个阶段,还可能出现启动失败的情况。
-
inject:插件的依赖声明,插件启动前会声明需要的系统能力,框架会等待所有依赖就绪后再启动插件,启动顺序由依赖关系决定,和插件的编写顺序无关。
-
事件总线:插件之间不直接互相调用,而是通过事件总线实现通信。一个插件发出通知后,所有监听该事件的插件都会收到通知,发送方不需要知道接收方的存在。事件共有五种分发方式,其中waterfall模式可以实现拦截逻辑,每个监听者可以选择继续传递流程或者终止执行。
三、动态加载的三大关键机制
Fiber:让插件的加载与卸载有迹可循
每个插件实例都有对应的Fiber记录其状态,完整的生命周期流程为:排队等待依赖 → 启动中 → 运行中 → 卸载中 → 已销毁,也可能在启动阶段失败。当插件声明的依赖未满足时,会一直处于排队状态,不会报错或崩溃,开发者可以通过遍历插件状态来排查未就绪的插件。热重载也是通过这套状态机实现的,修改插件代码或配置文件后,框架会先卸载旧的插件实例,再加载新的代码并重新启动。
如果需要排查处于排队状态的插件,可以通过以下代码遍历查看:
for (const runtime of ctx.registry.values()) {
for (const fiber of runtime.fibers) {
if (fiber.state === FiberState.PENDING) {
console.log(`${fiber.name} 在排队等待,缺少某个依赖`)
}
}
}
inject:依赖关系决定启动顺序
inject不仅在启动时检查依赖,还会持续跟踪依赖关系。即使配置文件中插件A写在插件B前面,只要A声明依赖B的能力,框架也会让A等待B加载完成。如果运行中某个依赖的插件被卸载或替换,所有依赖该能力的插件都会自动卸载,待依赖恢复后再重新加载,避免出现插件持有失效引用的问题。比如更换默认的Shell执行插件,所有使用Shell的插件都会自动重启并使用新的实现,不需要修改任何代码。
effect:清理插件的副作用
动态加载的难点在于卸载时清理副作用,比如定时器、事件监听器、子插件等。Cordis的effect机制可以自动管理这些副作用:通过框架API注册的操作会被自动跟踪,插件卸载时框架会自动撤销这些注册。对于框架未直接管理的资源,比如自定义定时器,可以将清理逻辑放入ctx.effect()返回的函数中,插件进入卸载阶段时,框架会自动执行这个清理函数。另外,清理函数的执行顺序是注册的反序,多个异步清理可以并发执行,如果需要顺序执行的清理步骤,可以放在同一个清理函数中依次执行。
这里给出一个简单的effect使用示例:
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => console.log('tick'), 200)
return () => {
clearInterval(timer)
console.log('heartbeat cleaned up')
}
})
}
四、实战案例:工具运行时的完整链路
我们可以用工具运行时组件来直观理解这套架构的落地:工具运行时本身是一个系统能力,会自动注册到框架中,它提供了6种事件类型,其中4种是waterfall拦截型事件,2种是广播型事件。它声明依赖“提示词组装”能力,只有当该能力就绪后才会启动,启动时会注册需要的提示词片段。
工具运行时还提供了工具注册方法,调用后会返回对应的卸载函数,可以动态卸载指定工具。工具的执行流程是四段式的waterfall流水线,审批插件可以在执行前拦截工具调用,结果屏蔽插件可以在执行后修改返回结果,让大模型接收到修改后的内容。
同一个工具运行时可以服务多个不同的Agent,每个Agent可见的工具集是通过多层继承计算而来的:先继承全局工具,再叠加所在组合包的工具,最后应用访问限制过滤,不同Agent的工具权限互不影响。
五、架构总结与未来展望
DeepSeek Harness的插件化架构是当前开源Agent框架中设计最彻底的之一,它不仅仅是“支持插件”,而是整个产品本身就是插件的组合,任意环节都可以在运行时替换。这种设计为个性化Agent开发提供了极大的灵活性,不同场景可以通过更换插件组合来适配不同的工具集、上下文策略和执行流程,大模型、工具、Agent循环甚至前端呈现方式都可以独立替换。
未来自定义Agent预设、第三方插件生态、场景化插件模板都会是重要的发展方向,尽管目前Harness还是v0.1的开发者预览版,接口可能随时调整,但这套架构的方向已经非常清晰,其“任意环节可运行时替换”的设计值得关注。

