DeepSeek Harness 近期披露的核心架构显示,其并未采用一个承载模型、工具、会话等能力的固定内核,而是基于 Cordis 插件架构组织整个 Agent 运行时。模型、工具、沙箱、文件系统、Agent 循环、调度与 UI 均由插件提供,Cordis 本身只负责上下文管理、服务注册、事件分发、依赖激活和副作用回收。这种设计把「Agent 能做什么」从静态代码结构转移到启动时挂入的插件树,也让 Agent 系统更接近一个可装配、可回收、可隔离的运行时。
从 Eclipse、OSGi 到 Cordis:插件架构被重新做轻
插件架构并不是新概念。Eclipse 在二十多年前就把 IDE 拆成插件、扩展点和扩展,宿主声明可扩展位置,其他插件通过清单贡献菜单、编辑器和处理逻辑。OSGi 更进一步,为 bundle 提供安装、启动、停止、更新和卸载等生命周期,并通过共享服务注册表完成发布、查找和绑定。当服务离开注册表时,依赖方必须同步处理动态变化。
Cordis 的设计中可以看到这些成熟机制的影子:动态服务、生命周期、注册表、声明式依赖和事件通知都有先例。但它没有把这些能力直接搬成重量级模块系统,而是将其简化并重组,使其更适合 TypeScript 应用内的细粒度组装。
与 OSGi 以 bundle 为部署单元不同,Cordis 的执行单元是 Fiber,一个函数或一个 Service 子类就可以成为插件。与 Eclipse 由宿主定义结构化扩展点不同,Cordis 让服务和类型化事件直接成为扩展面。对于 Agent 运行时而言,这一点尤为重要:工具、提示词片段、模型适配器和审批策略变化频繁,如果每次扩展都引入沉重的模块边界,开发团队很可能绕过框架,最终回到全局数组式的实现。
Cordis 官方仓库将其定位为「Meta-Framework of Spatiotemporal Composability」。从源码看,所谓「元框架」意味着它不规定 Agent、Web 服务或机器人必须包含哪些组件,只提供构建框架所需的基础语义。DeepSeek Harness 在其上定义 sessions、tools、llm、agents 等服务,再用这些服务组装产品。二者的关系更接近运行时机制与领域框架,而不是通用插件平台和插件集合。
依赖不再靠启动顺序:服务注册与状态机接管初始化
Cordis 的根对象是 Context。它同时承担依赖容器、插件挂载入口和事件入口。服务通过稳定名称出现在 ctx 上,例如 ctx.tools、ctx.llm、ctx.sessions。插件声明 inject 后,Cordis 只有在所需服务可用时才会激活它;服务消失,依赖插件也会进入卸载或等待状态。这意味着配置文件中条目的先后顺序不再承担启动顺序,依赖关系才承担启动顺序。
这种处理针对的是传统插件系统长期存在的问题:隐含初始化顺序。常见实现会遍历插件数组,依次调用 init。当工具依赖文件系统、文件系统依赖沙箱、UI 又依赖会话时,数组顺序会变成一套没有类型、没有诊断的依赖图。后续插入一个插件,顺序约束可能跨越几十个文件。
Cordis 将依赖写进 inject。Fiber 会为每项依赖保存当前实现,缺少依赖时保持 PENDING,依赖齐备后进入 LOADING 和 ACTIVE,启动失败则进入 FAILED。状态机让故障至少有了准确位置,而不是把问题埋在某个未定义的执行顺序里。
- Context 是代理对象,插件读取服务前必须声明依赖。
- 未声明就读取会抛错;声明了但服务当前不可用,也会抛出另一类错误。
- ctx.get(name) 可用于读取可选服务,但调用方需要显式接受服务可能不存在。
- 服务定义、服务提供方和消费方被完整建模,便于替换底层实现。
DeepSeek Harness 将这种关系称为 capability seam。以文件系统能力为例,定义方稳定调用协议,提供方可以接入本地目录或远程沙箱,消费方则把能力转换成模型可见工具。当沙箱被替换时,Shell、PTY、LSP 只要依赖同一能力面,就可以整体迁移到新的执行环境,消费方无需知道提供方运行在本机还是远端。
空间可组合:多 Agent 并存需要局部覆盖能力
「一切皆插件」的前提是一切产品能力皆由插件贡献,但 Cordis 自身仍保留插件运行所需的机制。上下文代理、Fiber 状态机、服务存储、事件总线和 Loader 属于元层。换句话说,系统并非没有内核,而是内核不拥有模型、工具和循环等产品特权。
在同一个进程中,服务名称必须稳定,但实例不能总是全局唯一。两个 Agent 可能选择不同模型、不同工具集、不同 persona 和不同沙箱。如果 ctx.llm 永远指向一个全局对象,插件替换只能发生在进程级,无法支持多会话并存。
Cordis 通过 Context.isolate 为指定服务名创建 realm 标签。服务注册和查找都以该标签定位,同名服务可以在不同子上下文里各自存在。两个 isolate 调用传入同一标签时会加入同一 realm;使用新标签时彼此隔离。子上下文通过原型继承父上下文,隔离映射按层遮蔽,局部配置不需要复制整棵容器。
DeepSeek Harness 又增加了 dsh-scope。它用不透明对象作为 scope key,维护父子关系,并创建带路由身份的事件 receiver。注册视图沿父链向下继承:Agent 能看到所属 preset 的提示词和工具。事件则沿链向上接纳:preset 级监听器能收到其下 Agent 的事件,兄弟 Agent 之间互不串线。
这两层空间模型分别解决不同问题:isolate 处理「哪个 tools 服务实例」,dsh-scope 处理「同一工具注册表里,哪些注册项对当前 Agent 可见」。前者适合替换提供方,后者适合对注册内容分层。如果所有差异都做成独立服务实例,会放大内存和初始化成本;如果所有差异都塞进一个全局注册表,过滤规则又会散落在调用点。
时间可组合:热更新、卸载和回滚都要可回收
插件能挂载只是静态组合。运行期间服务上线、配置改变、插件失败或上下文销毁,系统还要回到一致状态。Cordis 用 Fiber 和 effect 管理这条时间轴。每次插件应用都会产生一个 Fiber,记录父上下文、原始配置、解析后配置、依赖实现快照、生命周期状态和 disposables。插件调用 ctx.effect() 注册副作用,effect 的执行结果返回 disposer。事件监听、服务提供、子插件和访问器最终都进入这套所有权体系。
Fiber 卸载时会按注册逆序执行清理,并等待异步清理达到静止状态。若插件先启动子进程,再注册输出监听,最后暴露服务,销毁时就需要先撤销服务、停止新请求,再移除监听,最后结束进程。资源创建顺序的反向通常就是依赖安全的拆卸顺序。
传统插件常给出一个 deactivate() 钩子,把清理责任完全交给作者;漏掉一个定时器或事件监听,热加载几次便出现重复执行。Cordis 让每次注册同时产生撤销动作,框架持有所有权。DeepSeek Harness 的 vendor 版本还处理了更多重入场景:effect 在执行 setup 前先登记所有者包装;插件发布事件时,观察者可能同步卸载它;异步 cleanup 已经开始后,其他调用者仍能等待同一次清理;Fiber 处于 UNLOADING 时拒绝创建新 effect。
DeepSeek Harness 还把 Cordis 源码直接放进 vendor/,固定在明确的上游提交,并将包名重映射到 @deepseek-ai 命名空间。项目维护的本地修改包含 Fiber 重入卸载加固、配置更新事务、HMR 精确监听、延迟配置解析等。这增加了维护成本,但换来了框架层的可审计性。对于能够执行 Shell、修改文件、访问网络的 Agent 系统而言,插件生命周期出错可能留下进程、监听器、终端模式或权限状态,本地代码白盒比黑盒依赖更可控。
对 Agent 工程化的意义:复杂但可被集中暴露
Cordis 的价值不在于发明了一个新的插件系统,而在于把动态服务、生命周期、依赖注入、事件总线和资源回收压缩进适合 TypeScript 应用的进程内模型。对于 Agent 系统来说,这种组合正好对应几个现实需求:模型适配器会频繁更换,工具集需要按会话隔离,提示词和审批策略需要可插拔,沙箱环境可能在本地与远端之间切换。
但这套架构并不轻。插件作者需要理解 Context 父链、服务 realm、业务 scope 父链和事件过滤方向。常见错误会表现为某项能力「看不见」或意外泄漏,而类型系统很难完全证明运行时挂载位置。DeepSeek Harness 通过 preset 挂载审计、包级 invariant 和真实组合测试降低风险,但并没有消除模型复杂度。如果团队只需要单进程单 Agent,直接引入整套空间语义可能显得过重。
测试面积也随之扩大。挂载成功只是第一条路径,还需要覆盖依赖晚到、依赖撤销、初始化失败、卸载重入、异步清理、热更新失败、回滚再次失败等场景。DeepSeek Harness 要求注册项证明 disposal,产品可见插件还要通过真实 Loader 组合测试。这部分成本不会因为框架存在而消失,只能被框架集中暴露。相比线上出现幽灵监听器和半更新状态,把问题前置到测试阶段更适合长期运行、持续变化的 Agent 系统。
原创文章,作者:点点,如若转载,请注明出处:https://www.dian8dian.com/deepseek-harness-cai-yong-cordis-cha-jian-jia-gou-rang