Skip to content

Instantly share code, notes, and snippets.

@acmerfight
Last active August 15, 2026 09:21
Show Gist options
  • Select an option

  • Save acmerfight/db2934b921475dfed1a14f508809438f to your computer and use it in GitHub Desktop.

Select an option

Save acmerfight/db2934b921475dfed1a14f508809438f to your computer and use it in GitHub Desktop.
插件不是外挂:读懂 DeepSeek Harness 的「一切皆插件」

插件不是外挂:读懂 DeepSeek Harness 的「一切皆插件」

三种插件架构:Hook、扩展 API 与插件化运行时

一段从 AFFiNE、Cordis、Effect 一路谈到 General Magic 的发言,真正想说什么?


先说答案

原话最重要的意思只有一句:

普通插件是在主程序允许的位置增加功能;DeepSeek Harness(下称 DSH)则尽量把主程序本身也拆成插件。

这不是说 DSH 可以随意替换进程里的任何代码,而是说它把模型适配器、工具系统、Session、Prompt 和 Agent Loop 等产品级组件,都放进了同一套可组合、可卸载的运行时。

作者以前在 AFFiNE 尝试过相近方向,所以看到 DSH 时产生了强烈共鸣。后面提到 Effect、General Magic 和 PocketJS,是在说明这种设计为什么吸引他,以及他如何理解自己的职业经历。

整段发言可以分成三层:

层次 内容 可信度
代码事实 DSH 的产品组件是插件;注册有生命周期;AFFiNE 有 DI、scope、override、dispose;Effect 有 Tag、Layer、TestClock 可以核对
技术类比 AFFiNE 与 DSH 在追求相似的可替换性;Cordis 与 Effect 有共同思想 有启发,但不能画等号
个人叙事 AFFiNE 像 General Magic;PocketJS 可能成为新 Flash 感受和展望,不是技术结论

一、先想象一个最小的 Agent

一个能工作的 Agent,至少需要四样东西:

用户任务
   ↓
Agent Loop:决定下一步做什么
   ├── 调用模型
   ├── 调用工具
   └── 把过程写入 Session

普通插件:主流程固定,只在预留位置插入代码

例如主程序允许插件监听:

模型调用前 → [插件可以做点什么]
模型调用后 → [插件可以做点什么]
工具执行前 → [插件可以做点什么]

这种架构很实用。Webpack 插件接入编译器预先定义的 hooks;Pi 扩展可以注册工具、命令、事件和 UI;VS Code 扩展通过 Contribution Points 与 Extension API 增加能力。

共同点是:主程序先决定哪些地方可以扩展,插件再从这些入口进入。

DSH:连主流程的重要组成部分也进入插件系统

DSH 官方架构文档明确列出:模型适配器、工具注册表、Session 日志和 Agent Loop 本身都是插件,可以从配置中替换。

因此它允许这样的组合:

同一个 Agent 接口
   ├── Agent Loop A / Agent Loop B
   ├── DeepSeek / 其他模型适配器
   ├── 本地文件系统 / 远程沙箱
   ├── 本地子进程 / 远程执行环境
   └── Web UI / Headless Runner

消费者依赖的是某项能力,而不是某个写死的实现。替换一个 provider,所有通过该能力工作的组件可以一起改变。

这就是原话中「任意逻辑默认能被第三方扩展」背后的真实含义:产品结构默认采用可替换组件,而不是只有少数外围功能允许安装插件。

但“任意”不能按字面理解。一个没有注册成服务、事件或可撤销 effect 的私有函数,仍然不能被第三方凭空替换。Cordis 运行时、JavaScript 进程和操作系统也依然构成底层基础。


二、为什么“卸载插件”是一个困难问题

安装插件很容易。真正困难的是:插件离开时,系统还能不能恢复干净。

假设一个插件做了四件事:

① 注册一个工具
② 监听一个事件
③ 提供一个模型服务
④ 启动一个定时任务

删除插件文件,并不会自动撤销这四件事。监听器可能继续存在,定时任务可能继续运行,其他组件可能还握着旧服务。

Cordis 因而同时管理两个方向:

Cordis 的空间组合与时间组合

空间:谁依赖谁

例如:

Agent Loop
   ├── 需要 LLM 服务
   ├── 需要 Tools 服务
   └── 需要 Session 服务

插件声明依赖。所需服务存在时,组件才能工作;provider 出现、消失或被替换时,运行时重新处理相关组件。

时间:插件留下了什么

插件注册工具、服务或监听器时,同时登记撤销方法。插件卸载后,运行时执行相应清理。

所以 DSH 文档中的一句话非常关键:registrations are effects that unwind when their plugin unloads。这里的 effect 不是“视觉特效”,而是插件对运行时产生的一项可追踪改变。

不过 Cordis 只能撤销纳入管理的改变。插件已经发出的邮件、已经调用成功的支付接口、直接写入的外部数据库,不会因为插件卸载自动倒退。


三、“DeepSeek 给出了数学证明”究竟证明了什么

这句话有事实基础,但容易被读成营销口号。

Cordis 团队公开了一篇 88 页的论文草稿《A Programming Paradigm for Spatiotemporal Composability》。论文建立了形式模型,讨论可逆 effects、动态依赖、组件组合与收敛性质。

它证明的是:

在论文定义的操作规则与假设成立时,组件的安装、依赖解析和撤销可以满足相应的数学性质。

它没有证明:

  • 任意 JavaScript 副作用都可逆;
  • 任意两个插件永远不会互相干扰;
  • DSH 的实际实现不可能出现生命周期 bug;
  • 只要用了 Cordis,所有代码自然就能热替换。

数学证明约束的是一个精确定义的模型。现实代码必须遵循这个模型,证明才有意义。


四、为什么作者会从 DSH 想到 AFFiNE

AFFiNE 表面上是一个类似 Notion 与 Miro 的工作空间,但团队曾希望它不只是一款应用,还能成为构建其他应用的底座。其编辑引擎 BlockSuite 也强调组件可以被 AFFiNE 和第三方共同复用、扩展。

这类团队经常遇到一个矛盾:

今天:为了产品交付,直接修改底层引擎
明天:再为一个产品需求加入特殊逻辑
后天:底层引擎充满产品特例
结果:所谓“通用底座”只能服务原来的产品

一种解决思路是把“需要什么能力”和“使用哪个实现”分开。

业务代码不直接写:

const storage = new AffineCloudStorage()

而是表达:

我需要 Storage 服务。

运行时再决定提供哪个实现:

Storage
   ├── AFFiNE 云端存储
   ├── 本地存储
   ├── 第三方存储
   └── 测试用假存储

这就是依赖注入(DI)最朴素的意义。

公开提交历史可以证实,EYHN 在 2024 年向 AFFiNE 提交了新的 infra framework;其中确实有 identifier、provider、override、scope、缓存与 dispose 等机制。BlockSuite 也拥有相近的容器与扩展设计。

RxJS 在这里负责另一件事:把不断变化的数据表示成可以订阅的流。例如当前文档、用户状态或同步状态发生变化时,订阅者收到更新。

所以原话中“DI 和 RxJS 结合管理业务对象生命周期”,可以理解为:

  • DI 负责对象由谁创建、依赖什么、何时释放;
  • RxJS/LiveData 负责状态如何变化、谁订阅变化;
  • 两者共同降低业务模块之间的直接耦合。

但这不等于 AFFiNE 已经实现了 Cordis 式的完整动态组合。AFFiNE 的公开代码能证明它有替换和生命周期基础设施,不能证明所有业务逻辑都能在运行中任意装卸。

原作者真正的情绪是:

我们当年想把“应用”做成“平台”,但没有完全实现;DSH 从第一天就把这个目标放在架构中心。


五、Effect 为什么也被扯进来了

Effect 是 TypeScript 的一套程序运行与资源管理体系。入门只需要认识三个概念:

概念 最简单的理解
Tag 一项服务的类型化名字,例如 DatabaseClock
Layer 如何创建这项服务、它还依赖什么、退出时怎样清理
Effect<A, E, R> 程序成功返回 A,可能以 E 失败,运行需要 R

因此,“把 Tag 看成 service key,把 Layer 看成 provider”是一个不错的入门类比。

两者的共同点是:

  • 依赖明确;
  • 实现可以替换;
  • 资源有生命周期;
  • 测试可以提供假的依赖。

但 Cordis 和 Effect 不是同一个东西:

Cordis / DSH Effect
主要关心插件怎样安装、依赖、重组和卸载 主要关心一段程序怎样执行、失败、并发和释放资源
可以在普通 TypeScript 代码外管理注册 计算通常要表达为 Effect
强调运行中的 provider 出现与消失 强调解释执行时提供所需服务

Cordis 论文也把 Effect-TS 列为相关但不同的工作。因此,“沿着 DSH 的思路会想到 Effect”成立;“把 DSH 做到底就等于 Effect”不成立。


六、TestClock 为什么让作者兴奋

假设程序规定:失败后十分钟重试。

如果代码直接调用真实定时器,测试可能需要真的等待,或者到处 mock setTimeout

Effect 可以让程序依赖一个 Clock 服务:

生产环境 → 真实时钟
测试环境 → TestClock

测试时把虚拟时间直接推进十分钟,等待中的 Effect 就会继续执行。于是时间相关测试可以快速、确定地完成。

但“充分 Effect 化的代码里没有 setTimeout”是一条编程主张,不是语言事实;“把 E2E 墙钟时间压缩到单测时间”也有前提:所有等待必须经过可替换时钟。真实浏览器、网络、数据库和第三方库中的原生定时器不会自动加速。


七、General Magic、Manus、VoidZero 和 PocketJS 在说什么

到这里,原话已经从技术分析转入个人回忆。

General Magic 是一家商业上没有成功、但对后来产业影响很大的公司。其成员后来参与或创建了 Android、iPod、eBay 等重要项目。

作者把 AFFiNE 比作 General Magic,是在说:

项目没有完整实现最初的宏大目标,但在那里工作过的人,可能把形成的能力和理想带到其他地方继续发展。

这是一种组织类比,无法像代码一样证明。

“AFFiNE 的核心成员去了 Manus、VoidZero”没有给出姓名和对应履历,公开材料不足以核实这个笼统表述。把它当作作者的内部观察更合适。

PocketJS 则是一个真实的新项目:它用很小的 Rust 核心,让 Solid、Vue Vapor 等现代组件运行在 PSP、浏览器 WASM 与无头测试环境中。

作者问“PocketJS 能不能成为新一代 Flash”,是在期待它成长为跨设备内容运行时。现阶段它还没有 Flash 当年的分发规模、创作者生态和平台地位,因此这只是展望。

原话中事实、类比与个人叙事的分层


八、逐项事实审计

原话中的主张 结论 应当怎样理解
DSH 不是普通 Hook 式插件系统 成立 产品级组件本身也进入插件树
Agent Loop 也可以是插件 成立 DSH 官方架构明确列出
插件卸载会撤销注册 成立但有限 只覆盖通过 Cordis 管理的 effects
任意逻辑都能动态替换 过度表述 必须先被做成服务、事件或插件组件
AFFiNE 做过 DI 与生命周期框架 成立 提交历史和公开代码可以验证
AFFiNE 已完整实现 DSH 式动态组合 证据不足 公开实现显示的是部分共同基础
DeepSeek 为此给出了数学证明 有依据,但范围有限 证明的是形式模型中的性质,不是任意现实代码
DSH 做到极致就是 Effect 类比,不是事实 二者有共同关注点,但运行模型不同
TestClock 能让时间测试立即完成 有条件成立 时间操作必须通过 Effect Clock
AFFiNE 像 General Magic 个人判断 是组织命运的类比
PocketJS 可能成为新 Flash 未来展望 当前无法验证

九、这段发言真正有价值的地方

抛开“第一个”“任意”“做到极致”等夸张表达,它提出了一个值得认真对待的问题:

插件系统究竟只是让用户增加功能,还是允许产品的基本构成被重新组合?

前一种思路追求稳定:核心团队定义边界,第三方在边界外扩展。

后一种思路追求开放:模型、工具、存储、执行环境乃至 Agent Loop 都尽量成为可以替换的运行时组件。

DSH 明显选择了后者。这会带来更高的抽象成本、更复杂的代码和更难理解的生命周期;换来的则是组合不同 Agent、替换能力 provider,以及让 Agent 检查和改变自身运行环境的可能性。

事实上,DSH 已经提供自指 Cordis 工具:Agent 可以检查当前运行时、定义动态插件、运行和停止它们。但这些动态插件目前只存在于当前进程内,重启后消失,也不会自动晋升为正式插件。

因此,最准确的总结不是:

DSH 已经实现了任意逻辑的自我进化。

而是:

DSH 把 Agent 的主要行为结构放进了一个可检查、可组合、可撤销的插件运行时,为进一步的自修改实验提供了基础。

这已经很特别,但它仍然是一套正在开发中的工程系统,不是魔法。


资料来源

DSH 与 Cordis

插件模型对照

AFFiNE 与 BlockSuite

Effect、General Magic 与 PocketJS


核对日期:2026-08-15。本文区分公开可验证事实、技术类比与第一人称经历;无法独立核实的私人会议、融资叙事和人员流向未作为事实采用。

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="650" viewBox="0 0 1200 650" role="img" aria-labelledby="title desc">
<title id="title">原话的三层内容</title>
<desc id="desc">从可验证事实,到技术类比,再到个人叙事与未来展望。</desc>
<defs>
<radialGradient id="bg" cx="50%" cy="48%" r="70%"><stop stop-color="#18213b"/><stop offset="1" stop-color="#080d18"/></radialGradient>
<filter id="glow"><feGaussianBlur stdDeviation="10" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
</defs>
<rect width="1200" height="650" rx="32" fill="url(#bg)"/>
<text x="70" y="72" fill="#f8fafc" font-family="ui-sans-serif,system-ui,sans-serif" font-size="34" font-weight="700">阅读这段发言,要分清三层</text>
<text x="70" y="108" fill="#94a3b8" font-family="ui-sans-serif,system-ui,sans-serif" font-size="18">越靠近中心,越能由公开代码和文档直接核对。</text>
<circle cx="600" cy="365" r="235" fill="#3b1f57" opacity=".38" stroke="#d946ef" stroke-opacity=".45"/>
<circle cx="600" cy="365" r="165" fill="#17365a" opacity=".82" stroke="#60a5fa" stroke-opacity=".7"/>
<circle cx="600" cy="365" r="93" fill="#115e59" opacity=".96" stroke="#5eead4" stroke-width="2" filter="url(#glow)"/>
<g font-family="ui-sans-serif,system-ui,sans-serif" text-anchor="middle">
<text x="600" y="340" fill="#ecfeff" font-size="22" font-weight="800">可验证事实</text>
<text x="600" y="371" fill="#ccfbf1" font-size="14">DSH 组件插件化</text>
<text x="600" y="394" fill="#ccfbf1" font-size="14">可逆注册 · AFFiNE DI</text>
<text x="600" y="227" fill="#dbeafe" font-size="21" font-weight="750">技术类比</text>
<text x="600" y="254" fill="#bfdbfe" font-size="14">AFFiNE ↔ DSH</text>
<text x="600" y="474" fill="#bfdbfe" font-size="14">Cordis ↔ Effect</text>
<text x="600" y="155" fill="#fae8ff" font-size="21" font-weight="750">个人叙事与未来展望</text>
<text x="390" y="530" fill="#f5d0fe" font-size="14">AFFiNE 像 General Magic</text>
<text x="810" y="530" fill="#f5d0fe" font-size="14">PocketJS 会不会成为 Flash</text>
</g>
<rect x="65" y="220" width="260" height="230" rx="20" fill="#111827" stroke="#334155"/>
<text x="95" y="260" fill="#5eead4" font-family="ui-sans-serif,system-ui,sans-serif" font-size="18" font-weight="700">读法</text>
<text x="95" y="302" fill="#cbd5e1" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">事实:查代码、文档、提交</text>
<text x="95" y="342" fill="#cbd5e1" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">类比:看相同点,也看差异</text>
<text x="95" y="382" fill="#cbd5e1" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">叙事:理解作者,不当证明</text>
<text x="95" y="422" fill="#64748b" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14">三层混在一起,就会显得玄。</text>
<rect x="875" y="220" width="260" height="230" rx="20" fill="#111827" stroke="#334155"/>
<text x="905" y="260" fill="#f0abfc" font-family="ui-sans-serif,system-ui,sans-serif" font-size="18" font-weight="700">常见误读</text>
<text x="905" y="302" fill="#cbd5e1" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">形式化证明 ≠ 没有工程 bug</text>
<text x="905" y="342" fill="#cbd5e1" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">设计目标 ≠ 已全部实现</text>
<text x="905" y="382" fill="#cbd5e1" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">思想相似 ≠ 同一种系统</text>
<text x="905" y="422" fill="#64748b" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14">“任意”“第一个”需要额外证据。</text>
<text x="600" y="625" text-anchor="middle" fill="#64748b" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15">原话有技术洞察,也有作者对旧项目的感情;两者都可以读,但不能混为事实。</text>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment