DeepSeek Harness背后的论文:让Agent在运行中改写自己
编辑|Panda
昨天,DeepSeek 接连发布了 DeepSeek V4 Pro 正式版 模型和 DeepSeek Harness 开发者预览版 ,AI 社区宛如过年,褒奖、评测、争吵接连不断。截至本文发稿,开源仅一天的 DeepSeek Harness,star 数已接近 9.5 万,fork 数也达到了 8.8k,并仍在持续上涨。
https://github.com/deepseek-ai/deepseek-harness
是的,DeepSeek Harness 非常受欢迎。而它受欢迎的一大原因,藏在一个很多人还没注意到的名字里: Cordis 。
先说 Harness 本身。它是一个跑在本地的编程智能体,可简称 dsh,读文件、跑命令、改代码、查资料,差不多就类似于 Claude Code。
真正让它区别于同类的是设计方式: 一切 皆 插件 。
模型接入、工具注册表、会话日志、审批策略,连驱动智能体运转的主循环本身,全都是插件。
想换搜索引擎、接自己公司的模型服务,改配置就行,不用动框架代码;给系统加功能就是挂一个新插件,卸载时注册的一切自动撤销,不留残余。
支撑这件事的就是 Cordis。它是一套以依赖注入和可逆副作用为核心的插件与上下文内核,来自 Koishi 生态,只负责插件的加载、卸载和依赖关系。 dsh 的所有具体组件都是不同插件,靠服务与事件协作,在配置层自由组合。
这套底座的一大用法,从界面的模式选择器里就能看到。
四档模式里,标准模式功能最全,PTC 模式让模型生成一段代码来组合多轮工具调用,极简模式只留一个终端和一个文本编辑器、系统提示词只有一句话,适合跑基准测试。它们都是在既有的插件树上做加减法。
而第四档「 创造模式 」不一样,它是一组自指的 Cordis 工具,是高级入口:选择这一预设后,Agent 可以检查当前运行时的插件树,并动态挂载或卸载临时插件。模型可以临时写一个事件监听器、注册一个新工具、提供一个服务,任务完成后再把它卸掉。
DeepSeek Harness 默认的四个模式以及使用创造模式构建的 Obsidian 笔记模式 这听上去有点像让汽车在高速公路上给自己换发动机,所以项目没有默认打开它,信任等级被标注为等同于 shell 访问权限。临时插件只存在于进程内存里,不写文件、不装包、不改配置,重启即消失。
自修改式 Agent 的难点之一是写完之后怎么把组件干净地拿下来 :一次自我修改留下的事件监听器、打开的连接、注册进去的服务,如果没有一条明确的回收路径,系统运行几个小时就会变成一堆无人认领的残留。dsh 的做法是把动态插件仍然放进已有的插件生命周期里,跑在 Cordis 的 Context 和 Effect 机制下,注册项有确定的清理路径。
这套机制的设计依据,已经写在 DeepSeek 同步发布的一篇论文里:《 A Programming Paradigm for Spatiotemporal Composability (时空可组合性的一种编程范式)》。作者包括北京大学的 Yifan Shi(同时属 DeepSeek)、张伟(Wei Zhang),以及 DeepSeek 的崔添翼(Tianyi Cui)。这不是一篇 Agent 论文,甚至几乎不谈模型,它是一篇编程语言理论的论文,八十页,正文里有二十多个定理和证明。
论文地址:https://github.com/cordiverse/paper
软件工程的基石是组合
论文开篇提出的问题已经颇有历史: 软件工程的基石是组合 ,把复杂系统拆成简单部件再拼起来。但传统的组合是静态的:函数调用、模块导入、类继承,编译期就确定,运行期不再变化,这套东西有极其丰富的形式化基础。
而现代软件越来越需要 动态 组 合 :组件在运行时被加载、卸载、重新配置。
论文的判断是,相比静态组合,动态组合方向的理论基础仍然欠缺,工业界的通行做法是绕开它。
作者拿 VSCode 做了实证。VSCode 的所有扩展跑在一个共享进程里,虽然扩展可以动态安装,但这个 Host 没有提供任何在运行时卸载单个扩展代码的机制。一旦某个扩展的 activate 执行过,禁用或卸载它就要重启整个 Host,影响所有已加载的扩展。论文统计了安装量前 100 的扩展,其中 87 个包含可执行代码,也就是说移除它们都需要一次重启。
VSCode 确实提供了 deactivate 钩子,但它只是 Host 进程终止时的优雅关闭回调,无法用于在线移除;而且这个钩子把副作用的销毁和副作用的产生(在 activate 里)分开了,破坏了关注点的局部性,完整清理很难被验证。
依赖那一侧也很稀薄。VSCode 提供了 extensionDependencies 用于声明扩展间依赖,但前 100 个扩展里只有 7 个对非内置扩展声明了它。更关键的是,扩展间交互没有结构化契约:通过 getExtension(...).exports 拿到的返回值默认是 any,消费方无法依赖一个受检查的接口。
那为什么还没有改?因为操作系统和容器编排提供了一个粗粒度的替代品。操作系统在进程粒度上提供时间维度的可组合性,容器编排在服务粒度上提供空间维度的可组合性。模块出问题就重启进程,服务依赖交给编排器。
论文对这个替代方案提出了具体的批评:每次重启都会丢弃全部进程本地的累积状态(缓存、连接、部分计算结果),重建它们要花数秒到数分钟;要在这期间维持可用性就得跑冗余副本,为了无法恢复单个组件而付出整机的资源开销。空间上,容器级编排无法表达共享同一地址空间的组件之间的依赖,还会给本可以是本地函数调用的交互引入网络开销。两种机制都工作在进程和容器的边界上,而现代系统的组合粒度正在不断变细。
这个粒度错配,正是 自演化 Agent harness 会撞上的墙。
论文在动机部分写道:一个未来的 harness 可能在持续服务请求的同时,生成并部署对自身组件的修改, 每一次这 样的 修改都是一次动态组合 。没有时间可组合性,每次自我修改都要一次全量重启,在高频率下累积的不可用时间相当可观,进行中的任务被反复打断;更糟的是,一次有缺陷的自我修改可能让恢复所需的那个进程本身失效。没有空间可组合性,每个模块都要自己去检测所依赖的模块何时出现、消失或换了身份,而且只能用临时手段;一个天真的代码替换策略可能悄悄弄坏依赖方,或者引入只在重载时才暴露的循环依赖。
把编译期的概念搬到运行时
论文的技术路线可以一句话概括:把 效应(effect) 和 余效 应 (coeffect) 这两个类型系统里的经典概念,从编译期的静态标注提升为运行时的机制。
效应系统描述一个计算对环境做了什么,余效应系统描述一个计算需要环境提供什么。
前者的谱系从 Moggi 的单子、Plotkin 与 Power 的代数效应一路到 Koka、Eff 和 OCaml 5;后者从 Uustalu 与 Vene 的余单子到 Petricek 等人的余效应统一静态分析。
论文指出, 这两套系统恰好对应动态可组合性的两个维度 ,但它们都是静态工具:效应在词法固定的作用域内被追踪、由编译期的 handler 处理,余效应标注是对执行前就确定的上下文做验证。而部署之后才加载的插件,没有任何固定的词法作用域能圈住它。
于是论文 把这两个概念具体化(reify)成运 行时可以直接 操作的对象 。
可逆副作用处理时间维度。一个副作用被建模为 Γ → Γ × (Γ → Γ) 类型的函数:作用于当前上下文,返回修改后的上下文,同时返回一个显式的逆函数。运行时把这些逆函数不断累积到一个称为累加器的复合函数上,卸载组件时把累加器整个作用一次,上下文就回到组合之前的状态。因为逆函数按施加顺序前置累积,累加器天然以后进先出的顺序回收,与资源获取的嵌套结构一致。