DeepSeek 终于推出了自己的专武 deepseek-harness(dsh)。口号就一句:Everything is a Plugin,一切皆插件。在 dsh 里,不只是工具、模型接入是插件,连 agent 的核心循环本身都是一个插件。插上就跑,拔掉就停,不用重启。
这听起来像工程炫技,但它的底层是一篇 88 页、74 个定义与定理的论文——《A Programming Paradigm for Spatiotemporal Composability》,北京大学与 DeepSeek-AI 合著。这篇文章试着把这套东西讲清楚。
一、两个老问题
论文把"动态组合"拆成两个独立的维度,各用一个生活场景来说。
退租问题(时间维度)
你租了间房子,住了两年:钉了钉子、刷了墙、装了吊灯。退租时房东要求完全恢复原样。麻烦在于,你刷完墙之后房东来修水管,又在你那面新墙上打了孔。现在你要搬走了,怎么让房子回到"你从来没来过"的状态?
这就是所谓时间可组合性:组件被移除时,能撤销它做过的所有修改——哪怕其他组件的操作已经插在中间。
电网问题(空间维度)
你家的电饭煲不需要知道电从哪个电厂来。插座标准统一,插上就能用。电厂检修停电,电饭煲自动停;来电了,自动接着干。
这是空间可组合性:组件之间的依赖被结构化地声明出来,谁需要谁、谁提供谁,由运行时统一管理。依赖出现,组件自动激活;依赖消失,组件自动停机。
现代软件的插件系统,这两个问题基本都没解决好。VSCode 的例子已经说明时间维度失守;空间维度同样惨——那 100 个扩展里只有 7 个声明了自己依赖什么,插件之间的调用靠伸手去拿,拿回来的是个 any 类型,契约全靠人肉。
二、论文的两个答案
每个动作,自带撤销键
论文解决退租问题的思路朴素到意外:让每一个改变环境的动作,顺手交出一个"撤销我自己"的函数。
类似 Word 的撤销键。你每敲一个字、每调一次格式,Word 都记了一笔"怎么撤销",这些记录可以无限叠加。论文里那个贯穿全文的类型签名,意思大概是:
效应函数:接收「环境现状」 → 返回「新环境」+「一张还原图纸」装修师傅每次动工,留下一张怎么改回去的图纸,运行时替你把所有图纸收成一本总账。组件卸载时,从最后一页往前执行,房子就恢复了。
在 Cordis(论文思想的工程实现)里长这样:
functionheartbeat(ctx: Context) { ctx.effect(() => {const timer = setInterval(() =>console.log('tick'), 200)return() => clearInterval(timer) // 撤销键 })}函数体在装载时运行,返回的清理函数在卸载时自动运行。起了定时器?返回怎么清掉它。注册了监听器?返回怎么摘掉它。注册工具、挂载子插件、声明服务,所有注册 API 都内置这套语义,撤销还可以自动组合——做了 A、B、C 三件事,系统自动按 C、B、A 的顺序撤。组件作者不需要写卸载逻辑,卸载是自动的、完整的。
有人会想到 React 的 useEffect 也有 cleanup,看起来很像。但 hook 不能出现在条件、循环、嵌套函数里,也不能是 async,效应没法自由组合。Cordis 的效应就是普通操作,这是本质差距。
每个需求,自带雷达
电网问题的解法是给每个插件一张需求声明表:
const myPlugin = { inject: ['fs'], // 我需要文件系统服务才能工作 apply(ctx) { ... } // 服务到位后,这个函数才被调用}插件不 import 任何具体实现,只声明需要 fs 服务——像电饭煲不关心电厂,只认插座。服务还没出现?插件安静等着,一行代码不执行,不会因为依赖缺失而崩溃;服务就位,自动激活。反过来,服务下线了,依赖它的插件自动停机(停机时把各自干的事干净撤掉),服务恢复后自动回来。还有个细节:提供者会等所有依赖者安全停机后才回收资源——物业检修总闸,会等这一路电上的电器都关了再动手。
两个答案最终统一在一个叫"上下文"(Context)的结构里,可以想象一块公共白板:每次擦写自动留底(时间维度),每个区域装了传感器(空间维度)。数学上,"声明依赖"本身也是一次可撤销的环境修改,所以两个维度是同一个结构的两面。
论文还证明了三条全系统层面的保证,说成人话:
从未存在过——多个组件交错运行时,卸载任何一个,系统精确回到它从未来过的状态,别人的东西一件不误伤; 不死锁——所有"等依赖者先走"的等待都是单向塌缩的,不可能成环; 折腾半天等于一步到位——不管装了卸、卸了装,系统最终状态和一开始直接写下最终配置完全一样,动态的历史不留幽灵痕迹。
这套东西不是论文配套的玩具。Cordis 脱胎于 Koishi 聊天机器人框架的插件运行时,在生产环境跑了四年,承载过 4000 多个社区插件。
三、这套方案给 agent harness 带来什么
理论说完了,下一个问题才是关键:DeepSeek 为什么选它做 agent 框架的地基?
论文结论里埋过一句预言:这套范式最有说服力的验证方向是自进化的 agent 骨架——agent 持续地生成、替换自己的组件。没有完整撤销的保证,每次自我修改都要重启丢状态;一次故障的自我修改,甚至可能毁掉恢复所需的进程本身。
对照这个范式,dsh 的收益能想到的有以下几条,每一条都踩在 agent 场景的真实痛点上。
长会话运行中换手脚,不丢状态
Agent 和普通软件最大的不同是会话很长。一个跑了三小时的调试会话,上下文、缓存、连接都是资产。在 dsh 里,换沙箱后端(本地换到 E2B 云沙箱)是一次插件替换:依赖它的工具自动停机、切换、复活,会话状态原样保留。换模型 provider 同理。传统做法里这两件事都意味着重启进程,对长会话是灾难。敢拆才敢换,拆得干净,换了才不留后患。
安全策略是个可插拔的层
Agent 的自主行为(跑命令、改文件、发请求)需要管控,而且不同信任级别需要不同强度。dsh 里审批和守卫本身就是插件,挂在工具流水线上——每次工具调用先过 approval 人审,再过 guard 策略。加一层安全策略等于加一个插件,不动任何工具代码;换个信任模式,换个插件组合就行。把安全逻辑写死在工具里的框架,每调一次策略都要发版。
给subagent发"工牌"
复杂任务里主 agent 常要派subagent:让它检索但不能写文件,让它分析但不能跑 shell。dsh 的 Scope 机制(子上下文树)把这件事变成纯配置:子上下文可以遮蔽或收紧父级的工具面,授予就是装载,收回就是卸载。因为卸载有完整撤销保证,收回后环境干干净净。没有可撤销性,动态收窄能力面就是个漏洞放大器。
一个能力崩了,会话不死
长时间运行的 agent,某个依赖崩掉是常态:检索服务超时,沙箱后端失联。dsh 的处理是让崩掉的插件走"失败即卸载"路径,分文不留地恢复,不阻塞兄弟组件;依赖它的部分优雅停机,服务恢复后自动回来。会话主体完全不受影响。对比"一个组件异常拖垮整个进程"的传统形态,这对可用性是质的提升。
一套生态,多种产品形态
Agent 的分发渠道天然分散:终端用户要 CLI,企业要 Web UI,自动化场景要 headless。dsh 用 Profile/Patch 分层叠加解决:同一套插件生态叠出 CLI、Web、无头、自动化接入等产品形态,每个形态就是基座加形态插件加裁剪 patch 的组合,核心能力零重复开发。甚至有个 Minimal 模式裁到只剩两个工具,专门跑基准测试。这种发行版自由,只有全员插件化的架构付得起。
agent 自我修改的地基
最远的一条:论文设想的"自进化 agent"——模型在运行中挂载自己写的插件,学了新技能就装上,任务结束就拆掉。dsh 在这里没有架构障碍,Creation 模式甚至内置了运行时插件实验能力。撤销键保证拆掉的技能不残留,依赖雷达保证装上的技能正确接线。一切皆插件,说到底是为这个终局准备的:agent 的能力边界,从编译期决定变成运行期决定。
四、诚实的另一面
这套设计有代价,论文和工程两边都自己承认了。
逆函数不校验。ctx.effect 不检查你交出的撤销键是否真能撤销,就像房东不验收退租清单。写错撤销逻辑的插件会静默地破坏恢复保证——论文明说这是组件作者的义务,不是运行时的职责。
白板外面的世界不可撤销。发出去的网络包、写到磁盘又被别人读走的文件——系统边界之外的操作只能扣留(发之前拦下)或补偿(再做一件事抵消),不存在时光倒流。
间接层的税。读代码时,ctx.fs 要跳到接口包看契约,再找到当前装配的实现包看逻辑,一跳三个包。学习曲线也真实存在——fiber 状态机、四种事件分发模式、inject 语义,每个都是前置知识。
总的来说,它用读代码的间接成本,换组合与替换的零成本。做平台、做生态、做要长期跑的 agent 运行时,这笔交易划算;做一次性项目,未必。
五、作为一个开发者:值得动手的插件方向
讲了这么多架构,最后落回地面。"一切皆插件"对一个普通开发者意味着什么?意味着你的 agent 可以按你的习惯轻松个性化。在 dsh 里写一个插件就是写一个普通包:声明依赖,实现 apply,装上就改变 agent 的行为,拔掉就干干净净。
下面这些方向都来自日常使用的真实痛点,而且都不需要动框架一行代码。
让 agent 记住你
最普遍的痛点是失忆。你上周告诉它"这个项目用某个技术架构"、"提交信息用中文",这周它全忘了。可以做一个记忆插件:监听会话里的用户偏好,写进本地记忆库,每次会话启动时注入上下文。几乎所有 agent 框架用户的第一个自制插件都是它——失忆最让人烦躁,而插件层恰好补得上。
给 agent 立规矩
Agent 执行命令没有确认环节,rm -rf、force push、改配置文件,你只能祈祷。挂一个守卫插件在工具调用流水线上:高危命令先问你(审批),策略红线直接拒(守卫)。审批和守卫本身可插拔,所以能按场景配不同强度——个人项目宽松些,公司项目逢写必审。
把重复开场白固化
每次开工都要对 agent 重复同一套话:先读 README,看下目录结构,跑一遍测试确认基线是绿的。注册一个自定义命令,比如 /kickoff,一键注入这套流程。再配合定时能力做每日例程——早上自动汇总昨天的改动、生成今日待办。
模型路由,好钢用在刀刃上
所有任务都用同一个模型,要么简单任务浪费旗舰额度,要么难任务被廉价模型搞砸。可以在请求层做路由:文件读取、简单查询走便宜模型,架构设计、复杂推理走旗舰。前文说的 waterfall 中间件链正好干这个——拦截每个请求,按任务特征改写它指向的模型。
长任务挂机自由
让 agent 跑半小时重构,你不知道它什么时候完成、有没有卡在等你确认,只能时不时切回来看一眼。监听"任务完成"和"等待审批"事件,推桌面通知或发到 IM,配合上面的审批插件:agent 干活你干别的,需要拍板时它来叫你。
放心让它试错
想让 agent 大胆尝试,又怕它改坏东西。做一个"试验配置":文件系统后端一键切到沙箱实现,让 agent 在沙箱里随便折腾,满意的改动提交,不满意的整段丢弃。这是一切可撤销送给使用者的礼物——试错的成本,从恢复备份降到拔掉一个插件。
这些方向的共同点:你对 agent 的不满,理论上都可以变成一个插件。框架提供骨架,体验由你自己长出来。
回到开头那篇论文。它把"插件装卸"拆成"撤销要完整"和"依赖要响应"两个正交维度,统一进一个自带记账的上下文结构,证明了这套机制在全系统层面不崩,然后在DeepSeek的agent框架走向了现实。
Everything is a Plugin 的底气,其实来自 Everything is Revertible——一切皆可撤销,才敢一切皆插件。
夜雨聆风