对 DeepSeek Harness 的学习与思考
昨晚睡前看到了 deepseek harness 发布的消息,但是太困直接睡觉了。今早一看已经 60 k+ star,这个涨星速度太夸张了。 正好今天周五了,没什么心思上班,于是从它的仓库开始,在我的 agent 帮助下,一点点梳理 deepseek harness 的设计哲学。暂时没有去看它的源码,只想弄明白一件事情:为什么 Everything is a Plugin? 整个 deepseek harness 构建在 Cordis 之上,仓库里的文档说得很明白: 它是一个小型运行时,其中的每项能力,包括工具、LLM(大语言模型)适配器、文件访问乃至 agent loop(智能体循环)本身,都是挂载到共享上下文中的插件。 插件系统的核心问题 这篇文章想解决的一个核心问题是: 一个组件能否在系统运行时被安全地动态加载、替换和卸载;当它所依赖或提供的能力发生变化时,相关组件能否自动、按照正确顺序调整自己的生命周期。 展开来说,传统软件中的组件、依赖之间的组合大多是静态的,一般通过模块导入、函数调用、类的继承等方式实现。在这些机制下,组件之间的组合关系通常在编译、链接或系统初始化阶段确定,并在后续运行期间保持相对稳定。 插件系统和长期运行的服务经常需要在运行时插入、替换和移除组件,文章将这类能力统称为“动态组合”。 为了解决上述问题,作者在文章中将其拆分成了两个相互正交但又必须同时成立的维度: 时间可组合性:组件删除后,它做过的事情能否全部撤销? 空间可组合性:组件依赖谁,依赖变化后谁先启动或退出? 注:时间指的是组件从加载到卸载的整个生命周期;空间指的是组件在依赖图中的位置。 Effect、Coeffect 和 Context 再来看文章中提到的两个名词:effect 和 coeffect。 Effect:程序对外部做了什么? Coeffect:程序需要外部提供什么? 在此基础上,文章把一个组件定义成以下公式:组件 = 所需依赖 + 提供能力 + 激活时执行的可撤销操作,这里重点关注“可撤销操作”。 传统的插件通常这样设计: activate(): 注册路由 注册监听器 启动定时器 打开连接 deactivate(): 删除路由 删除监听器 停止定时器 关闭连接 开发者需要手动维护一个组件的构造函数和析构函数,在组件数量庞大时,很容易出现构造函数中创建的资源没有在析构函数中进行回收,从而导致资源泄露的问题出现。 为了解决这个问题,文章提出了“可撤销操作”的概念。系统运行时会将组件的可撤销操作记录下来,并在组件移除时,按照先进后出的顺序逐项撤销,也就是典型的 栈式回收。如此一来,开发者不再需要单独维护一套与激活逻辑分离的组件级卸载流程,而是在创建每个原子 effect 时同时提供对应的 inverse,运行时负责追踪这些 inverse,并自动组合成完整的卸载过程。 ...