对 DeepSeek Harness 的学习与思考

喻灵 2026-08-14 18:16 1

昨晚睡前看到了 deepseek harness 发布的消息,但是太困直接睡觉了。今早一看已经 60k+ star,这个涨星速度太夸张了。


正好今天周五了,没什么心思上班,于是从它的仓库开始,在我的 agent 帮助下,一点点梳理 deepseek harness 的设计哲学。暂时没有去看它的源码,只想弄明白一件事情:为什么 Everything is a Plugin?



鉴于本人在该领域涉猎尚浅,以下梳理难免带有局限性,欢迎各位大佬批评指正。



整个 deepseek harness 构建在 Cordis 之上,仓库里的文档说得很明白:



它是一个小型运行时,其中的每项能力,包括工具、LLM(大语言模型)适配器、文件访问乃至 agent loop(智能体循环)本身,都是挂载到共享上下文中的插件。



插件系统的核心问题


顺着仓库中附带的论文链接,找到了这篇《A Programming Paradigm for Spatiotemporal Composability》。


文章想解决的一个核心问题是:



一个组件能否在系统运行时被安全地动态加载、替换和卸载;当它所依赖或提供的能力发生变化时,相关组件能否自动、按照正确顺序调整自己的生命周期。



展开来说,传统软件中的组件、依赖之间的组合大多是静态的,一般通过模块导入、函数调用、类的继承等方式实现。在这些机制下,组件之间的组合关系通常在编译、链接或系统初始化阶段确定,并在后续运行期间保持相对稳定。


插件系统和长期运行的服务经常需要在运行时插入、替换和移除组件,文章将这类能力统称为“动态组合”。


为了解决上述问题,作者在文章中将其拆分成了两个相互正交但又必须同时成立的维度:



  • 时间可组合性:组件删除后,它做过的事情能否全部撤销?

  • 空间可组合性:组件依赖谁,依赖变化后谁先启动或退出?


注:时间指的是组件从加载到卸载的整个生命周期;空间指的是组件在依赖图中的位置。


Effect、Coeffect 和 Context


再来看文章中提到的两个名词:effectcoeffect



  • Effect:程序对外部做了什么?

  • Coeffect:程序需要外部提供什么?


在此基础上,文章把一个组件定义成以下公式:组件 = 所需依赖 + 提供能力 + 激活时执行的可撤销操作,这里重点关注“可撤销操作”。


传统的插件通常这样设计:


activate():
注册路由
注册监听器
启动定时器
打开连接

deactivate():
删除路由
删除监听器
停止定时器
关闭连接

开发者需要手动维护一个组件的构造函数和析构函数,在组件数量庞大时,很容易出现构造函数中创建的资源没有在析构函数中进行回收,从而导致资源泄露的问题出现。


为了解决这个问题,文章提出了“可撤销操作”的概念。系统运行时会将组件的可撤销操作记录下来,并在组件移除时,按照先进后出的顺序逐项撤销,也就是典型的 栈式回收。如此一来,开发者不再需要单独维护一套与激活逻辑分离的组件级卸载流程,而是在创建每个原子 effect 时同时提供对应的 inverse,运行时负责追踪这些 inverse,并自动组合成完整的卸载过程。


Context 是组件与运行环境之间的统一接口。组件通过 Context 获取依赖(Coeffect)、提供能力和执行操作(Effect)。一方面,Context 追踪组件执行 Effect 时产生的 inverse,并将它们组合成该组件的整体恢复操作;另一方面,Context 承载当前可用的依赖绑定,并负责依赖的解析与变化通知。


总的来说,Effect、Coeffect 和 Context 各自的定位如下:



  • Effect:组件改变了什么?

  • Coeffect:组件依赖于什么?

  • Context:系统中的“改变”和“依赖”分别属于谁?


Finite State Machine


对于组件的状态,理想状态下只有两种:INACTIVEACTIVE



  • 当所需依赖满足时,由 INACTIVE 转到 ACTIVE,并执行组件初始化;

  • 当所需依赖缺失时,由 ACTIVE 回到 INACTIVE,并执行历史操作撤销;


对应的状态机如下:


INACTIVE <--> ACTIVE

这样的理想状态机,只能描述组件初始化和历史操作撤销都是瞬间完成的理想情况,而在实际的工程实现中,这两个阶段往往包含着大量的异步耗时操作,还可能出现多步执行或操作失败等情况。为了更好地描述工程实现中的组件状态转换,文章最终扩展定义了以下状态机:


INACTIVE --> LOADING --> ACTIVE
^ | |
| v v
+------ UNLOADING <-----+

这个状态机的目标只有一个:



组件的状态变化需要时间,在这个过程中,系统必须明确当前进行到了哪一步,并且此时允许他人对其进行哪些操作。



如何实现时间可组合性?


状态机保证:



  1. 进入 LOADING 时,每完成一个操作,就记录对应的撤销函数;

  2. 只有全部初始化完成,才能进入 ACTIVE

  3. 无论正常卸载、依赖变化还是初始化失败,都必须经过 UNLOADING

  4. UNLOADING 执行全部撤销函数后,才能进入 INACTIVE


即“组件被删除后,它产生的 effect 必须全部撤销”,因此不会出现“组件已经消失,但它所持有的资源还在”的情况。


如何实现空间可组合性?


状态机保证:



  1. 进入 UNLOADING 后,不再接受新的依赖者。

  2. 先卸载消费者,再卸载提供者。


总而言之,这个状态机是 Effect、Coeffect 和 Context 在系统运行时中的执行协议:



  • Reversible Effect 使得组件产生的状态变化能够被恢复;

  • Reactive Coeffect 使得组件的依赖变化能够被发现;

  • Context 为 Effect 和 Coeffect 的变化建立归属关系;

  • 状态机则保证它们在异步加载、失败和动态替换的情况下按正确顺序发生;


运行时为每个组件维护两个依赖视图。target 表示按照最新环境,组件现在应该绑定哪些 provider;committed 表示本轮生命周期实际绑定了哪些 provider。只要二者不同,当前生命周期就已经过期,组件必须先完整卸载,再基于新的 target 重新加载。这保证初始化过程不会前半段使用旧依赖、后半段使用新依赖。


通过上述机制,文章得出的核心目标是:



  1. 删除组件以后,系统应该像它从来没有加载过一样;

  2. 依赖发生变化时,系统应该自动调整相关组件,而不是让每个组件自己轮询和判断;

  3. 最终系统状态应该只取决于最终配置,而与中间经历了多少次加载、卸载和热更新无关;


在相应约束成立时,组件卸载后,系统能够恢复到观测上等价于该组件从未加载过的状态;依赖变化会自动驱动相关组件重新协调;系统最终的稳定状态只取决于最终组件组合,而不取决于到达该组合的动态历史。


对于 AI Agent 的工程实现价值


那么,上述机制对于一个 AI Agent 产品来说,有哪些价值?相较于现有的各种 Agent,又有哪些优势?我觉得可以从以下观点出发。


一、AI Agent 的运行时更容易发生结构性变化


传统的软件程序组件结构通常比较稳定,需要升级时进行重启往往可以接受。但是 AI Agent 的组件结构却经常变化,比如:



  1. 切换 LLM 供应商和 API 协议;

  2. 动态添加、切换、移除工具;

  3. 修改提示词和权限策略;

  4. 为不同会话挂载不同能力;

  5. 模型自己生成插件并尝试加载;

  6. 长任务执行期间替换某个能力实现;

  7. ……


这点我在开发 Z3r0 的过程中就深有体会:沙盒容器的可用状态直接影响了 Agent 的可用工具集,在没有插件化支持的场景下,一旦外部依赖的状态发生了变化,就只能销毁并重建一个 Agent 实例,造成不小的资源开销。


在这种场景下,如果没有严格的动态组合机制,长期运行就会出现重复工具、旧 Provider 引用、孤儿进程和无法解释的状态污染等问题。


二、让 AI Agent 可以安全地修改自己


目前的各种 Agent 都可以编写、修改代码,甚至通过自主编写的代码来扩展自己的能力。但是“能够写代码”不等于“能够安全应用代码”。让 Agent 自主完成扩展,会面临着以下问题:



  1. 旧版本产生的资源是否全部撤销?

  2. 新版本加载失败时能否恢复?

  3. 依赖旧版本的组件怎么办?

  4. 迟到的旧请求还能不能修改新版本状态?

  5. ……


为了应对这些问题,deepseek harness 的扩展子系统已经把 Plugin、不可变 Package 版本和每次激活的 Run Identity 分开,并提供了 definerunstopundefine 等完整生命周期操作。也就是说,它把模型生成代码视为受管理组件,而不是直接 eval 后永久留在进程里。由此带来的价值便是:



AI Agent 的自我进化从一次危险的全局突变,变成一系列可追踪、可撤销的组件转换。



三、把资源所有权变成结构规范


对于一个 AI Agent 系统来说,很容易创建长生命周期的资源,例如:子进程、定时任务、MCP 连接、LSP 与 PTY 会话等。如果将资源创建和清理分别写在 start()stop() 中,那么 LLM 生成的代码或频繁修改的插件很容易忘记同步维护,造成资源泄漏。


按照文章中的观点和 deepseek harness 的实践,将“Registrations are effects”写成仓库级强制规范:所有贡献都经过 ctx.effect()ctx.on(),registry 的 register() 必须返回 disposer;甚至规定 dispose 不能只“发送停止请求”,而必须等待工作真正停稳,以免留下孤儿进程。


这样的做法对 AI 生成的代码显得尤为重要,因为结构性约束通常比“请记得清理资源”的提示更可靠。


DeepSeek Harness 更独特的一点


与常见的一些 AI Agent Framework 相比,deepseek harness 的区别对比如下:

































体系 主要解决的问题 区别
OpenAI Agents SDK Agent、tools、handoff、manager、session、guardrail 和运行生命周期 重点是一次或多次 Run 中如何协调 Agent,没有把任意运行时组件的安装与 inverse、依赖重解析统一成核心语义。
Claude Agent SDK Functions、hooks、subagents、权限和项目配置扩展 hooks 很适合确定性检查和审计,subagents 适合隔离上下文与并行工作;官方文档展示的重点不是插件卸载后的完整 Effect 恢复和依赖者重协调。
LangGraph 图工作流、checkpoint、暂停恢复、持久状态和故障后续跑 强项是恢复“任务执行位置”。其官方文档要求副作用封装为 task 并设计为幂等,以应对重执行;论文关注的是撤销一个长生命周期组件已经安装的贡献。
DeepSeek Harness / Cordis 运行时组件的安装、卸载、依赖变化和局部恢复 直接把可撤销副作用、响应式依赖、作用域 Context 和异步生命周期组合为同一运行时协议。

在 Cordis 的基础上,deepseek harness 还将持久化事实存储与可撤销运行状态给拆开了。官方原则是“模型可见即已记录”,即任何进入模型请求的内容都必须能够从 Session Log 重建。这就形成了一种非常适合 AI Agent 系统的双层设计:



  • 控制平面可撤销:工具、Provider、策略和插件可以动态替换;

  • 事实平面可重建:模型看到过什么、工具做过什么由持久日志保存;


总而言之,我认为 deepseek harness 的优势在于它让动态修改的所有权、依赖、恢复方式和失败边界变得显式,并尽可能由运行时结构保证。这种机制对于固定工具、短生命周期、执行完即销毁的简单 Agent 来说,可能显得冗余笨重,但是对于长期运行、可扩展、多会话、能生成代码并修改自身能力的 Agent,它提供的是接近操作系统级别的运行时基础,也就是所谓的 AgentOS。

最新回复 (1)
  • Aichiyu 08-14 21:16
    1

    写的真好啊,佬友,deepseek harness 可能产品比较简陋,但是想法确实很好 ^-^

* 帖子来源Linux.do
返回