← 返回观点 思考

定律先于代码:DeepSeek Harness 的可组合性设计论

「可组合性优先」的理论提炼:一条元约束、八个维度、十二条设计论题、五族可移植的律。DSH 不是靠缩小组合空间获得可预测,而是把每个组合维度代数化。多数团队不必 fork——律可以整族抄走,本文给出收割清单与三条路决策树。

2026-08-18思考55 分钟阅读

当任意两个插件相遇,谁说了算?多数框架的回答是"看注册顺序"。DeepSeek Harness 的回答是一组能写进代码评审清单的定律。

这是 DSH 系列第三篇。第一篇讲竞争格局,第二篇拆了源码工程结构,这一篇回答一个更根本的问题:当我们说一个框架"插件化"时,到底在要求什么。agent 系统比传统软件更怕这个问题,因为 agent 的工作负载天然是高密度组合:循环、工具、策略、记忆、沙箱、UI,每一层都有人想插一手,每一手都可能踩到别人。

引子:组合失控的三种死法

8 月 13 日深夜,DeepSeek 把 Harness 以 MIT 协议开源。三天里 star 冲到 3.97 万。热闹之下,一个安静的事实更值得注意:这个 219 个包的仓库,任意两个第三方组件怎么组合,是有答案的。而多数号称插件化的系统,组合起来靠运气。

先看三种常见的死法。它们不是假想敌,是几乎所有插件化系统长到第二年会撞上的墙。

死法一:hooks 角力。 两个扩展都要改同一条请求:一个往上下文里注入检索结果,一个做敏感信息脱敏。框架给了带优先级的 hooks,于是执行结果取决于注册顺序。脱敏先跑,注入的内容就绕过了它;注入先跑,用户又报告"脱敏后模型看不见关键信息"。维护者的答案是"调整加载顺序试试"。这类 issue 永远关不完,因为冲突没有语义,只有时序。换一台机器、改一次配置,结果就变。修 bug 的人只能祈祷复现环境别变。

死法二:深合并地狱。 配置有四层:框架默认值、发行版预设、用户目录、命令行参数。深合并之后,一个字段的最终值来自哪一层,没有人能机械回答。你改了没生效,是因为三层之上还有一层;你没改却变了,是因为上游默认值悄悄换了。工程师 debugging 配置问题的时间,随层数线性上涨,随字段数线性上涨,两者相乘。多数平台不是死于功能不够,是死于配置不再可推理。

死法三:清理顺序不确定。 插件 A 注册时拿了插件 B 的句柄,卸载时 B 先走了,A 的清理代码悬空。热重载越频繁,泄漏越明显。更糟的是不可复现:时序相关的问题从不留下稳定的现场,报告里只能写"偶尔泄漏,重启解决"。

三种死法有一个共同的名字:组合失控。单个插件都是对的,组合起来没人负责。传统软件里组合失控是维护成本问题,agent 系统里它是安全问题:策略插件、权限插件、工具插件组合出的行为缺口,就是越权通道。

区分两个词。可配置是开关多;可组合是任意开关组合有定律。hooks 系统是可配置:两个 hooks 冲突时,结果取决于注册顺序与运气。waterfall 加单调 guard 是可组合:任意监听器组合下,结果仍由定律给出。这个区分是理解 DSH 架构的钥匙,也是判断一个 agent 框架是真平台还是单体外壳的分界线。

可配置与可组合
可配置与可组合

一条元约束,八个维度

DSH 源码里所有关键设计共享同一条约束:

任意第三方组件的组合必须保持可预测,不依赖"组合恰好被核心团队设计过"。

面对这条约束有两条路。传统 harness 靠缩小组合空间:单体核心加有限 hooks,第三方想改核心行为,走审核、走 PR、走核心团队的仲裁。这条路在生态小的时候有效,生态一大,仲裁队列先爆,扩展者等不起,索性 fork,生态碎片化。DSH 走另一条:把每个组合维度代数化。每个维度有显式的组合律,任意组合的语义可计算,核心团队不需要逐个组合把关。

八个维度,一张表看全:

维度 业界常见做法 DSH 机制 组合律
生命周期 init/dispose 回调对 effect 栈 撤销序是注册序的镜像
状态 可变对象加旁路日志 事件日志 会话语义=fold(日志)
拦截 优先级 hooks waterfall 包裹可结合,否决吸收
策略 对称 allow/deny 链 单调 guard deny 是吸收元
能力 模块直接 import 三角色缝 提供者替换=配置变更
执行 硬编码本地 执行世界 工具是世界的纯消费者
配置 深合并 patch 代数 整行替换,层序可判定
信任 边界加角色 per-call 能力 权限随调用衰减

表是索引,四行值得展开看机制。

生命周期这行,effect 栈的含义比"回调对"深一层:插件在上下文里做的每一次注册,订阅事件、开定时器、起子进程,都把对应的撤销动作压进栈。卸载是栈回退,逆序、幂等,启动到一半失败也有良定义的中间态。死法三在结构上不存在:清理顺序永远是注册顺序的镜像,不存在"A 的清理等 B 先走"的悬空。

状态这行,fold 是个数学词,含义很朴素:当前状态不是存在一个可变对象里,而是把日志从头到尾"重放"出来的结果。日志每追加一条事件,状态就是全部事件的累积投影。想改历史?不存在这个操作,追加新事件可以,改旧事件没有入口。这听着重,实际是增量的,投影缓存让重放成本只跟新事件数有关。

策略这行,单调 guard 的关键是裁决空间只有两个值:维持现状,或拒绝。多个 guard 参与时,裁决等于"是否存在一个 deny",与顺序、重复、个数全部无关。翻案这个动作在裁决空间里没有表达,不是被禁止,是写不出来。

配置这行,patch 代数的意思是:配置不是一棵被反复合并的树,而是一张行表。每层配置只有两个动作,插入新行,或按 id 整行替换某行。最终状态是空表上按层序 fold 的结果。想知道某个字段的值从哪来,找最后一个 touch 这行的层,答案唯一且机械可判定。

诚实地说边界:八个维度里六个被形式化为可检验的定律,对应源码 E/L/W/G/S/P 六族,各自带实现锚点;执行与信任两维停在机制层,有机制、有默认安全,但没有形式化保证。这不是缺陷,是精确:形式化到哪里,评审检查项就能跟到哪里;没形式化的部分,不假装有保证。

还有一个命题值得单独说。DSH 的会话状态与模型可见输入,等于四个参数的函数:激活插件集、patch 序列、事件日志前缀、scope 树。外部世界,文件系统、网络、凭据解析,显式声明为自由变量。传统系统难调试的三类噪声在这个设计里被结构性消除:隐式全局状态没有了(一切状态是日志的投影),无记账注册没有了(注册即压栈,可回放),乱序提交没有了(工具结果按模型序提交,abort 时未启动的调用补合成错误结果,日志永远能重建模型见过什么)。

检验这个命题的方式也很硬:resume 与 replay 走同一条验证路径,从日志构造出的会话必然能再次持久化。你不可能造出一个"只能活在内存里"的会话。对做评测和对照实验的团队,这句话的分量不言自明。

十二条论题,四根轴

DSH 的设计立场可以浓缩成十二条论题。比清单更重要的是它们从哪来:每条论题都是一根压力轴上的取舍。agent 平台逃不开四根轴。

时间轴问什么会变、以什么节奏变。状态轴问什么必须可审计、可重放。交互轴问多方参与时顺序算不算数。信任轴问跨边界时谁说了算、不知道时怎么办。十二论题分坐四轴:T1 循环是插件、T7 执行是世界、T11 能力经由缝在时间轴;T2 事实不可变、T3 注册即效应、T8 配置是代数在状态轴;T4 拦截只有一个动词、T5 策略必须单调、T12 组合按作用域裁剪在交互轴;T6 能力随调用携带、T9 类型图延伸到线、T10 诚实胜过完整在信任轴。

挑六条展开。

T1:循环是插件。 依据:agent 域最大的变量是循环本身,推理范式按月迭代,从单步工具调用到多代理编排到概率性规划,每次范式转移都是循环重写。落地:DSH 把 agent loop 做成插件,官方架构文档写明"换循环要更新架构文档",意思是循环替换是受支持的一等操作,不是 hack。循环内核只剩一个状态机骨架,策略全部外置。代价:框架税先于产品验证到期,循环策略已收敛的团队,这套抽象是纯开销。信号:产品视野 18 个月以上且循环策略未收敛,值得;一次性交付,不值得。

T2:事实不可变,视图可推导。 依据:LLM 系统最难的是行为归因,模型为什么这么做。归因要求历史不可篡改,且视图可以重新定义。落地:会话日志 append 即深冻结;上下文压缩只替换"表面投影",也就是模型所见的那层视图,原始记录永远可重放。审计、回放、fork、对照视图,全部由这个结构保证,不需要额外机制。代价:全量持久化的存储成本,zstd 压缩缓解;投影一致性要持续维护。信号:需要审计、回放或对照实验的系统,采纳;纯前进式对话可以退化为可变历史加备份。

T4:拦截只有一个动词。 依据:多个拦截动词组合时必然角力,死法一的根源。落地:waterfall 是唯一拦截机制,监听器包裹 next 链,不调 next 就是否决,否决是一等行为而非异常路径。拦截点有四个:步进前(改写或否决本步)、请求前(改请求)、流上(包装流式输出)、工具执行前后(全生命周期)。循环核心零策略分支。收益是第三方要改核心行为时,不需要核心团队仲裁优先级。代价:拦截表达力低于直接改核心,异步瀑布的编写有心智成本。信号:第三方扩展要改核心行为,采纳;只有自家扩展,hooks 够用。

T5:策略必须单调。 依据:多方决策的结果必须与参与顺序无关,数学上格加吸收元是顺序无关的最小构造。落地:guard 在工具执行前只有两个选择,维持现状,或给出拒绝理由。关键在实现位置:决策类型里没有 allow 构造子,这不是纪律约定,是类型上写不出违反。多个 guard 的拒绝理由怎么呈现有显式规则,但理由只是裁决的载荷,不影响裁决本身。代价也很实在:它表达不了翻案。安全 guard 拒绝的,后续用户 allow 翻不了案。这类需求要走新事件,审批链产生新的 decided 事实,不是改律。律的边界也是设计出来的。信号:策略来源三个以上(用户、团队、安全),必须;单一来源,可选。

T8:配置是代数,不是合并。 依据:深合并的最终值来源不可判定,是配置地狱的根源。落地:patch 只有两种操作,插入新行,或按 id 整行替换;系统状态是空表上按层序 fold 的结果。"改了没生效"变成可回答的问题:找最后一个 touch 这行的层。加载器换层是事务性的,新层先导入成功再处置旧层,失败回滚,热重载不留半态。代价:没有部分字段继承,子层要整行写。信号:配置层数三层以上,采纳。

T10:诚实胜过完整。 依据:信任边界靠"不假装"维系,partial 伪装成 full 比缺失更危险。落地:沙箱后端自报 full 或 partial,Windows 的 ACL 沙箱诚实标注 partial;审批无人应答即拒;能力事实驱动 schema,模型看到的工具面由真实存在的能力决定,沙箱不在就不给模型看沙箱参数,不给它许愿不存在参数的机会。代价:功能清单显得少。信号:生产环境与合规场景,必须。

其余六条一张表收住,判定式都一样:依据、落地、代价、信号。

论题 一句话 采纳信号
T3 注册即效应 部分失败的回滚必须良定义 需要热重载或动态加载
T6 能力随调用携带 权限衰减跟随调用链,不随主体固化 agent 链跨权限域
T7 执行是世界 远程化收敛为 N+M,不是 N×M 本地远程双执行
T9 类型图延伸到线 接口、类型、线上协议三位一体 多客户端共享一个 host
T11 能力经由缝 围绕"什么会变"划分模块 有两个以上候选实现
T12 组合按作用域裁剪 继承与隔离用一条原语解决 单进程多 agent

四根轴还能当生成器用。遇到新设计问题,先问它压在哪根轴上、顶在哪条论题的背面。比如"审批流要支持多数投票":多数投票是非单调聚合,与 T5 冲突。要么把投票折叠成单个 decided 事件(DSH 的做法),要么清楚知道自己在离开 T5 的保护范围。反过来,轴上找不到论题的地方就是新空白:预算与成本约束在四轴之外,DSH 也没有对应论题。这个空白下一篇会回来。

律是可抄走的资产

DSH 最有移植价值的东西不是代码,是定律。八族律,其中六族形式化为可检验的定律,另有作用域与调度两族伴生律,每条消灭一类顺序 bug。挑五条代表,每条看三样:律说什么、靠什么机制成立、消灭什么。

E1 逆序律。 撤销序是注册序的镜像:A 依赖 B 先注册,清理时 A 必须先走。机制是 effect 栈,注册即压栈,卸载即栈回退,幂等且部分失败有良定义状态。它消灭清理顺序不确定,死法三的解药。

W3 否决吸收。 监听器不调 next,整条链的结果就是"未发生"。否决是一等显式行为,不是异常路径,也不需要优先级仲裁。核心实现约十行。它消灭分散拦截的优先级角力,死法一的解药。

G1 吸收律。 多个 guard 的裁决只依赖"是否存在 deny",与顺序、重复次数、个数无关。成立的关键在类型层:决策类型没有 allow 构造子。它消灭策略注册顺序改变裁决。

P1 可判定所有权。 任何配置字段的最终值等于"最后一个 touch 它的那层的整行"。来源可机械判定,深合并歧义在结构上不存在。它消灭配置地狱,死法二的解药。

L1 指称律。 会话语义唯一由事件序列决定,别无隐藏状态。一切"当前状态"是日志的 fold,一切"模型所见"是投影的派生。它消灭隐式全局状态与"改历史破坏重放"。

汇成一张表:

机制 消灭的顺序 bug
effect 栈逆序 清理顺序不确定
模型序提交 工具完成序不等于因果序
单调 guard 策略注册顺序改变裁决
patch 整行替换 深合并歧义
waterfall 唯一动词 分散拦截的优先级角力
表面投影 改历史破坏重放
scope 向上准入 事件可见性泄漏到无关 agent
per-call 能力 低权调用链接触高权资源

这些律不依赖 DSH 的基础设施。guard 单调律只需要一个不含 allow 的类型;waterfall 核心约十行;patch 代数约一百行 loader。收割的单位是律加最小实现,不是包。

律怎么变成设计?拿"执行搬去远程"走一遍五步法,这是仓库里真实发生过的迁移。

需求:把 agent 的全部执行——文件、进程、终端、LSP——从本地搬到 E2B 远程沙箱,不改工具代码。第一步计数:N 类工具乘 M 个执行环境,等于 N×M 套远程适配。假设二十类工具、四种沙箱,就是八十套适配,不可持续。第二步找论题:T7 说执行是一个世界,工具是世界的纯消费者。把文件路径解析和可执行文件查找的语义绑定到“世界”这个参数上,组合数塌缩成 N+M。第三步操作路径:换 fs-e2b 和 subprocess-e2b 两个 provider,Bash、PTY、LSP 零改动。抽象的边界也被显式文档化:“必须与执行同处一个世界”的决策(比如 LSP 的语言服务器进程跟代码住在一起)留在世界内,不靠踩坑发现。第四步找律:T8 patch 代数保证迁移是配置变更,--dump-config 的输出与实跑一致,可评审、可回滚。第五步反推:没有世界抽象,搬一次远程等于每个工具重写一遍;即使搬完,下次换执行环境再来一遍。

第五步最常被跳过,也最有价值。反推段落就是设计文档里"不采用的备选方案"章节,被否替代项由此机械产出。

最后一条使用提醒:律表达不了,和律被违反,是两回事。G1 表达不了翻案,这是设计边界,翻案需求要走新事件;而一个 allow 构造子混进 guard 类型,这是违反律,是 bug。混起来会导致两种错误:把 bug 当边界放过,或者把边界当 bug 硬修。

三十年谱系,一份代价清单

DSH 不是凭空出现的。把时间线拉长,它站在一条三十年血统的末端:1968 年 THE 分层系统提出层级分解;1985 年 Mach 微内核把"什么进内核"变成显式问题;2000 年 OSGi 给 Java 带来服务注册表和 bundle 生命周期;2004 年 Eclipse RCP 做出扩展点体系;2015 年 VS Code 扩展宿主让插件模型在产品端软着陆;2020 年 Koishi/Cordis 把这套东西做成 TypeScript 原生的 effect 栈插件框架,用在聊天机器人域;2026 年 DSH 全套移植进 agent 循环。

三十年谱系
三十年谱系

相对前人,DSH 有三条实质推进。循环本身可换:VS Code 从未让编辑器核心循环可替换,DSH 让 agent loop 成了插件。配置组合是显式代数:OSGi 的 config-admin 没有做到无深合并的层序律。类型化扩展:declaration merging 加类型生成,让注册表里的服务和事件有编译期类型,OSGi 时代的注册表没有这个。

时机上有一个反常规的选择。VS Code 是先做产品、约一年后才开放稳定扩展 API;DSH 把框架化做在产品成熟之前。依据和 T1 一致:循环还在按月迭代,硬编码等于每次转移重写。这个赌注的另一面是概念税先于收入。

换一个视角看,agent 域正在压缩重演平台史:2023 到 2025 年是单体 CLI 产品,2025 年出现 hooks 和 skills 的受限扩展,2026 年出现全量框架化。OS 和平台花三十年走完的辩论,agent harness 三年走完。区别在于节奏:平台史是产品成熟后才开始平台化,agent 域是平台化与产品验证同时进行。这是机会也是风险,机会在于格局未定,风险在于没人付得起等待成本。

历史的镜鉴是 Mach 对 Linux。Mach 的概念,微内核、capability、client-server,长期看全赢了;Linux 赢了当下市场。对 DSH 应持双重预期:若循环范式收敛固化,微内核的边际价值下降;若持续迭代,这套架构是护城河。

代价清单也要摊开讲。概念存量约 45 个一等概念,合格 TS 工程师从跑起来到能改核心插件估计两到四周,到能独立设计缝与 patch 层要一到两个月。热路径有常数成本:每个 assistant chunk 一次 JSON 快照加深冻结,事件全量落盘。算法复杂度没有问题,处处有增量缓存,投影增量与全量重算永远同值,这是正确性优先换来的常数。

还有一份过工程候选清单,各自成立但各有通病:逐文件 100% 覆盖硬门禁对框架团队正确、对产品团队奢侈;JSONL 与 SQLite 双持久化后端长期双维护,社区终将收敛其一;多客户端类型生成在单客户端场景下是过度投入;Windows ACL 沙箱诚实标注 partial 是加分,但 SID、DACL、硬链接的维护面与用户份额不成比例。治理总账:219 个包、双语文档门禁、估计需要两到四名核心维护者。小团队照搬这套流程,会被流程本身拖垮。

你能拿走什么

不是每个团队都该 fork。四个问题走完决策树:产品视野是否 18 个月以上且循环策略未收敛?团队是否 5 人以上且能常驻 1-2 人维护框架层?能否接受 rc 期 breaking,锁 commit 自担迁移?是否需要审计回放?前三问都是,深用;卡在第三问的,收割 P1 加观察上游。第一问否而需要审计,收割 P1 前两步;都不满足,收割基础件后就够。

收割与深用的决策树
收割与深用的决策树

收割:不引一行 DSH 代码,移植定律。一周量级的 P0 清单六件:

模式 最小实现量 消灭的 bug
单调 guard 一个类型加一个 fold 策略顺序赌局
waterfall 统一拦截 约 10 行加纪律 拦截点 if 意大利面
patch 整行替换 约 100 行 loader 深合并地狱
模型序提交 调度器 committed 指针 工具结果因果混乱
fail-closed 默认 纪律 静默降权
能力事实驱动 schema getter 加条件字段 模型许愿不存在的能力

适合 95% 的团队。收益立得,风险为零,唯一要防的是移植走样:律要带全。guard 不带类型约束就只是约定,约定会被 deadline 打败;waterfall 不配“唯一动词”纪律就退化成新 hooks,角力换个地方重演。

收割还有第二档。P0 之上是季量级的四件套,顺序有讲究:effect 栈插件内核、事件溯源会话、三角色缝、工具管线,每件是下件的地基。没有 effect 栈,热重载不可靠;没有事件溯源,管线决策不可审计。走到这一档的团队,拿到的已经不只是模式,是可维护的平台层。

深用:锁 commit 或 fork 核心五件套,会话、循环、工具三个核心包加 vendored 的 cordis 和 include,在其上建产品。前提是 5 人以上、视野 18 个月以上、能常驻维护框架层、接受 rc 期颠簸。fork 的生命线是 vendored Cordis 的修改日志:本地每一处补丁都有记录、有理由,上游演进时合并窗口靠它撑着。把它当合同维护,不是当 README 写。

观察:已有自建栈的团队,等会话格式版本升到 1 以上、API 有稳定承诺再评估。rc 期的格式没有迁移承诺,此刻在它上面建生产插件生态,等于把地基租给别人。

一句话版:视野不足 18 个月或循环已收敛,收割 P0 后自建循环;平台团队且接受颠簸,深用;其余观察。

尾声:会执行,不会学习

把 DSH 的事件日志摊开,数一遍下游消费者,七类:resume、fork、transcript、遥测、标题、统计,外加测试期的快照回放。一切行为可重放,一切决策可审计,一切模型可见输入可重建。换句话说,经验采集器的全部底料已经就位,存储成本也已经在付。

唯独没有第八类消费者:从历史中提取经验并反哺系统的 learner。没有策略优化,没有 prompt 演化,没有工具裁剪建议。架构把路修到了门口,学习回路本身是空白。

对做下一代平台的人来说,这块空白比已实现的部分更有价值。下一篇展开一份完整的参考设计:第八类消费者怎么进场,它的每一次适应为什么必须走"提案、审批、落层、观测"的受审管道,以及为什么说 DSH 的单调策略半格恰好是自我改进系统的安全底座。


声明: 本文基于 DeepSeek Harness 开源仓库(github.com/deepseek-ai/deepseek-harness,v0.1.0-rc.5,分析基线 commit 47f9438,2026-08-17 复核无漂移)源码阅读撰写,系列前两篇见本站。文中判断为作者观点,不构成投资建议。数据截至 2026 年 8 月 17 日。