乐于分享
好东西不私藏

从插件树到 Session Log:DeepSeek Harness 的复杂概念怎样连成一套系统

从插件树到 Session Log:DeepSeek Harness 的复杂概念怎样连成一套系统
上一篇从“Everything is a Plugin”看 DeepSeek Harness 的设计谈到,DeepSeek Harness 的复杂并不完全来自 Developer Preview 阶段的粗糙。它把 Harness 的许多内部选择开放给构建者,目标是让构建者控制 Agent 怎样被组织和运行。这篇继续向架构内部走一步:当Everything is a Plugin成为整个系统的前提,DeepSeek Harness 用什么办法避免插件化变成一团混乱?
DeepSeek Harness 是一套 Agent Runtime。模型在其中生成文本或结构化工具调用,Runtime 负责组织模型收到的内容、调度工具并保存任务状态。Session Log 用只追加的事件记录一次会话。按照官方架构文档,负责接入模型的适配器、工具系统、会话机制和任务循环都由插件提供,产品层没有一组永远不可替换的特权组件。DeepSeek Harness Architecture
开放到这个程度,系统首先要确定一次启动加载哪些插件。选出的插件进入运行期以后,又要取得依赖、替换实现并清理自己留下的状态。当工具、上下文和任务循环都能被插件改变,已经发生的过程也需要成为可以重建的记录。这些问题会依次带出后面的术语,沿着装配、运行和记录的顺序连成一条设计链。

Everything is a Plugin 把 Agent 变成一棵插件树

DeepSeek Harness 底层使用Cordis组织插件,Cordis 是负责依赖、事件和生命周期的插件运行框架。这里的Plugin是由 Cordis 挂载到运行上下文中的组件,它可以提供服务,也可以注册工具或事件监听器,生效和退出都受同一套生命周期管理。插件的范围远大于通常意义上的工具扩展,就连默认任务循环与会话日志自己也是一类插件,所以构建者不仅可以改变 Agent 会做什么,也可以改变它怎样运行以及怎样保存状态。
Bundle是配置与代码的分发单位。它把一组 Cordis 配置行连同这些配置行要挂载的代码打包起来,供不同的启动组合复用。官方的 dsh-base 是每个 Profile 都会加载的基础层,模型接入、工具和持久化等通用能力从这里进入系统,沙箱与审批也包含在内。dsh-web-app 增加浏览器应用,dsh-headless 增加不启动服务器的一次性任务执行器。它们描述的是可以叠加的能力层。
Profile是一套有名字的启动组合。它按顺序列出本次运行需要的 Bundle,也可以加入不属于这些 Bundle 的外部插件。官方提供了 web 和 headless 两个 Profile 模板:web 在 dsh-base 上叠加 dsh-web-app,headless 则在 dsh-base 上叠加 dsh-headless。前者提供浏览器界面,后者执行一个任务、输出结果以后退出。Standard Mode 的默认组合面向代码工作,装配出来的是 Coding Agent。
Patch处理一套组合在具体部署中的局部差异。加载顺序依次是 Profile 列出的 Bundle、Profile 自带的 Patch、用户目录 Patch 和命令行 --patch,后一层可以覆盖前一层。Patch 以配置行的 id 为目标,覆盖时替换该行的整份配置,也可以插入新行。例如,一个团队沿用 web Profile,却要改变其中的沙箱设置,就可以覆盖对应配置行,无须复制和修改 dsh-base 或 dsh-web-app。
Bundle 只有进入 Profile,才成为本次运行采用的一层;Patch 作用在已经选定的层之上。一个 Bundle 可以被多个 Profile 复用,同一个 Profile 也能在不同部署中保留少量配置差异。解析完成以后,这些选择形成一棵有挂载关系的插件树。Standard Mode 是其中一种已有装配结果;换一套 Profile,DeepSeek Harness 可以呈现为另一种 Agent。
插件树只解决了“本次启动有哪些组件”。它还没有回答插件进入运行期以后怎样找到依赖,以及某个实现被替换或卸载时,系统怎样避免留下失效的监听器、工具和服务实现。DeepSeek Harness 把这部分运行期关系交给 Cordis 管理。

Cordis 让插件在运行时建立关系,也能退出关系

Cordis Context是插件共享的服务容器,也是插件获得生命周期的运行环境。插件由 Cordis 启动后,不直接依赖其他插件的具体代码,而是通过 Context 中约定好的 Service 名称(ctx.)调用所需能力。Service 背后的实现可以替换,使用这项 Service 的插件不必随之修改。Cordis Primer
插件通过 inject 声明自己需要哪些 Service。比如一个插件需要会话服务,它先在 inject 中写明这项依赖;会话 Service 尚未就绪时,该插件不会启动。依赖离开以后,Cordis 也能让相关组件退出当前运行状态。Cordis 所说的空间组合性就发生在这里。组件声明自己需要什么,运行系统根据当下存在的依赖建立关系,无需每个插件自行猜测加载顺序。
仅有稳定的 Service 名称,还不足以保证某项能力可以替换。DeepSeek Harness 用Capability Seam划定一项能力可以替换的范围。以文件操作为例,fs 包通过 ctx.fs 定义统一的文件操作接口,面向模型提供文件工具的 tool-fs 只通过这份接口发出请求。具体操作可以由 fs-local 在本机完成,也可以交给 fs-sandbox 或 fs-e2b。这些实现都是Provider,tool-fs 则是Consumer。只要 Provider 遵守 ctx.fs 的接口与事件约定,更换文件系统实现就不需要修改 tool-fs。Service Definition 规定双方共同遵守什么,Provider 决定能力怎样实现,Consumer 负责使用这项能力;三者围出的替换范围就是 Capability Seam。Capability Seams
Profile 完成启动时的组合选择以后,Capability Seam 规定一个实现要在什么接口边界内被换掉。这种抽象确实让我又想起了 OpenStack 的阴影,尤其是厂商给 OpenStack Neutron 写适配代码的时候。Provider 的替换成本取决于接口是否完整,也取决于 Consumer 有没有绕过接口依赖内部细节,这些问题要到代码走读和案例中检验;概念本身先把“可以换”落实成了接口、实现与使用者之间的关系。
插件之间还需要通信和拦截运行过程。Typed Event是带有明确名称、类型和分发语义的事件接口。它给扩展行为规定可识别的入口,插件可以通过事件交换状态,也可以在指定流程节点接入策略。到 Agent Loop 部分,同一套事件机制还会承担另一项工作:把需要长期保存的事实与只服务当前运行的控制信号分开。
动态系统最麻烦的地方通常发生在退出时,Linux 内核对 rmmod 如此谨慎,是因为模块代码可以卸载,指向它的回调却未必已经消失。在 DeepSeek Harness 中,插件注册了一项工具、一个 Provider 或一段事件监听,如果卸载只删除插件对象,这些登记仍可能留在运行环境里。Cordis 把这类需要随插件生命周期撤销的登记称为Reversible Effect。注册发生时会留下对应的撤销动作,插件被卸载或重载时,Cordis 调用这些动作,清除该插件在当前作用域中登记的内部状态。Cordis Lifecycle and Effects
Reversible Effect 的作用范围是 Cordis 管理的内部登记,包括工具、监听器和 Provider。已经提交的数据库变更与生产环境中的其他操作位于这个范围之外,需要事务、补偿动作或人工处置。插件系统能否重载和替换,很大程度上取决于旧组件退出后会不会继续污染新的运行状态,所以内部 Effect 的撤销依然很重要。
Cordis 用Spatiotemporal Composability概括这套设计。空间组合性管理组件在同一时刻怎样按照依赖建立关系,时间组合性处理组件进入、替换和退出以后,内部 Effect 怎样随生命周期撤销。Spatiotemporal Composability 这个术语本身看起来很科幻,大概也是我看到最常被批评为“自嗨”的概念。关于它是否德不配位,我会在读完论文后单独写一篇。
Profile 和 Bundle 让插件树能够被装配出来,Cordis 则让这棵树在运行中保持关系。Agent 接到任务以后,还需要一套所有组合都能遵守的工作节奏,否则每种 Profile 都可能发明自己的模型调用、工具执行和结束条件。

Agent Loop 给不同组合规定同一套运行语义

前文所说的Runtime,是 DeepSeek Harness 位于模型与外部环境之间的运行层。它接收用户输入,组织上下文并请求模型;模型返回工具调用以后,Runtime 负责调度执行,并把结果保存到会话中。Agent Loop是发生在 Runtime 内部的任务循环,从一次输入开始,经过模型请求与工具调用,直到这一轮任务结束。Agent Loop 本身也可以由插件提供,但不同实现都要遵守 DeepSeek Harness 统一规定的任务边界和事件协议,Runtime 才能用同一种方式调度并记录这些运行过程。Agent Lifecycle
官方架构用TurnStep划分这段过程。Turn 是从一项输入开始,直到系统不再欠下待处理工作的一轮运行,它可以包含零个或多个 Step。Step 是一次模型请求,以及这次请求触发的工具调用。Turn 划定一轮任务的运行边界,Step 把每次模型请求及其工具执行标记成可定位的单位。
举一个最简单的例子。用户问“README 第一行写了什么?”,从这条输入进入系统,到 Agent 给出答案,是一个 Turn。第一个 Step 中,Runtime 请求模型,模型返回读取文件的工具调用,文件内容写回会话;第二个 Step 中,Runtime 带着工具结果请求模型,模型给出最终答案,本轮不再有待处理工作,Turn 在这里结束。一个 Step 可以包含多个工具调用,划分 Step 的依据是模型请求次数:Runtime 使用工具结果再次请求模型,就进入了下一个 Step。
一次任务开始运行以后,前面的概念会进入同一条执行链。Profile 先选出 Bundle 与 Patch,Cordis 把相应插件挂载到 Context,插件通过 Service 取得依赖,并注册工具或 Provider。用户输入到达 Agent 后,Agent Loop 开启一个 Turn;进入 Step 时,Runtime 从会话历史组装模型需要的消息和工具定义。模型返回普通文本或结构化工具调用,Runtime 解析调用并送入工具执行管线。管线在执行前接入策略检查,规则要求审批时会暂停执行并等待批准,随后由对应 Provider 完成调用,工具结果进入会话。Tool Execution Pipeline
模型生成调用,Runtime 解析和调度,Provider 执行工具,人的批准只发生在规则要求的节点。把这些动作分开,才能看清 Harness 承担的工程工作。Agent Loop 根据结果判断当前 Turn 是否仍有待处理工作;需要模型继续处理工具结果时,它开启下一个 Step,没有待处理工作时,本轮结束。
这一过程中会经过几类EventSession Event是写入会话、需要跨重载保存的持久事实,例如输入、模型消息、工具调用和工具结果。Agent Event服务正在运行的 Agent,处理输入到达、状态变化、请求或停止等实时控制,不以长期保存为首要目的。Capability Event则把策略和适配器接到某项具体能力的调用过程中,例如工具执行前的审批与改写。
前一节的 Typed Event 主要解释插件怎样通信;到了 Agent Loop,它增加了一层更严格的区分。影响会话历史的内容要成为 Session Event,当前运行中的协调交给 Agent Event,能力调用周围的扩展由 Capability Event 接入。插件仍然可以改变运行方式,但它必须选择相应的事件域,不能把持久事实和临时控制混成同一种消息。

Session Log 保存模型看见过的运行事实

用只追加的事件记录会话,是 Session Log 的第一层作用。DeepSeek Harness 同时把它作为会话状态的事实来源,模型消息历史由这条事件流派生,分叉、恢复、转录、遥测和持久化也可以围绕同一历史建立。Session README
官方架构文档给出了一句直接的约束:Model-visible means logged。任何会进入模型请求的内容,都要能够从 Session Log 中的事件重建。一个插件可以给模型增加提示词片段、工具结果或其他上下文,但这些内容一旦影响模型下一步看到的输入,就不能只存在于某个插件的临时内存里。新的模型请求要从已有事件派生,生成的消息、工具调用与结果继续写回同一条事件流。
Session Log 同时服务于构建者和 Runtime。构建者用它检查一次任务的输入、输出、调用和状态变化,Runtime 则从中构造下一次模型请求的上下文。这个观测窗口本身就是运行语义的一部分。插件可以扩展模型上下文,Session Log 要求这些扩展留下可以重建的来源,Everything is a Plugin 由此获得一条状态约束。
本文把Trajectory限定为运行历史面向人的路径视图,构建者可以按照步骤查看一次任务经历了哪些模型请求和工具调用。当前官方架构文档没有把 Trajectory 定义为与 Session Log 并列的独立状态模型,所以本文只把 Session Log 视为事件事实来源,Trajectory 用于描述人怎样阅读这条运行路径。两者在产品界面中的精确对应关系,留到使用篇根据届时版本核对。
这套可见性有两条明确边界。Session Log 保存 Harness 能够观察的模型输入与输出、工具调用和状态变化,不包含模型没有交给 Runtime 的私有思维过程。它能够重建某一步的模型输入,却不承诺确定性重放;模型服务、外部数据和工具环境发生变化以后,同一组历史事件可能得到不同结果。它让人能够确认模型当时收到过哪些输入、Runtime 执行过哪些动作,但开放世界不会因此变成一段可以无损倒带的录像。

每一项开放性都对应一条架构约束

沿着一次任务走完以后,DeepSeek Harness 的概念已经连接成了一套运行结构。Everything is a Plugin 扩大了可替换范围,Profile、Bundle 与 Patch 先把选择变成一棵可重复装配的插件树。Cordis 通过依赖、Capability Seam 和生命周期控制这棵树的运行关系;Agent Loop 用 Turn、Step 与事件域规定任务怎样推进;Session Log 把模型可见内容收回到一条能够重建的事件历史中。
这套设计最吸引我的地方,是它没有把开放性当成一句口号。实现能够替换,是因为 Capability Seam 先划出了能力接口;组件动态挂载以后,Cordis 会继续跟踪依赖变化,并在退出时清理内部状态。插件进入 Agent Loop,要遵守 Turn、Step 与事件协议。任何扩展一旦改变模型收到的上下文,Session Log 就要求它留下能够重建的来源。前一个设计带来的自由,会在后一个概念中遇到约束。DeepSeek Harness 的复杂主要生长在这些配对关系里。
AI Workflow Owner来说,这些抽象对应的是可以修改和检查的控制位置。他可以用 Profile 选择一套 Agent 组合,通过 Capability Seam 接入本专业流程需要的数据和执行环境,并从 Session Log 检查模型收到的上下文、工具调用与结果。最终使用者可能只看到一个经过封装的 Agent,构建者却能在运行系统中找到修改和检查的位置。DeepSeek Harness 面向的首先是负责设计和维护工作流的人,这一点与上一篇的判断保持一致。
我目前愿意给这套概念设计很高的评价,因为插件的自由从启动装配一直延伸到运行历史,每一层又受到下一层的约束。不过,我还要继续观察这些美好设想能否建立起完整而有活力的生态。拿编程语言作类比,我认为 DeepSeek Harness 很像早期的 Python 或 Ruby:它们都带着很高的设计愿景出发,后来却形成了大相径庭的生态,其中一些差异恰恰来自早期很难评价的抽象选择。DeepSeek Harness 会走向哪一种结果,可能要很久以后才能回答。

附录:本文术语表

下面的位置按照当前文章的小节和段落标记。一个术语第一次出现时如果只做了简要说明,后面才完成正式定义,两处位置都会列出。

总纲与运行系统

Harness(开篇第 1 段):模型外侧负责让 Agent 持续完成任务的运行系统。它组织模型收到的内容,连接工具和外部环境,并保存任务继续运行所需的状态。
Agent Runtime / Runtime(首次出现于开篇第 2 段;正式定义见“Agent Loop 给不同组合规定同一套运行语义”第 1 段):DeepSeek Harness 位于模型与外部环境之间的运行层。本文后续使用 Runtime 指代这层实际接收输入、请求模型、调度工具和保存结果的系统。
Everything is a Plugin(开篇第 1 段;集中解释见第一节):DeepSeek Harness 的基本设计原则。模型接入、工具执行、会话机制和 Agent Loop 都可以由插件提供,产品层不保留一组永远不可替换的特权组件。
Standard Mode(“Everything is a Plugin 把 Agent 变成一棵插件树”第 3、5 段):DeepSeek Harness 面向代码工作的默认组合,装配以后呈现为 Coding Agent。

插件树的启动装配

Cordis(第一节第 1 段;第二节继续展开):DeepSeek Harness 用来组织插件依赖、事件和生命周期的插件运行框架。
Plugin(第一节第 1 段):由 Cordis 挂载到运行上下文中的组件。插件可以提供 Service,也可以注册工具或事件监听器,其生效和退出由同一套生命周期管理。
Bundle(第一节第 2 段):配置与代码的分发单位。一个 Bundle 可以被多个 Profile 复用,但只有被某个 Profile 选择以后,才会进入本次启动组合。
Profile(第一节第 3 段):一套有名字的启动组合。它按顺序选择本次运行需要的 Bundle,也可以加入外部插件和上层 Patch。
Patch(第一节第 4、5 段):针对具体部署进行局部覆盖的配置层。它按配置行的 id 替换整行配置或插入新行,无须复制原有 Bundle。

Cordis 的运行期关系

Cordis Context(第二节第 1 段):插件共享的 Service 容器,也是插件获得生命周期的运行环境。
Service(第二节第 1、2 段):插件通过 ctx. 访问的命名能力。使用者依赖 Service 的公开约定,无须直接依赖实现这项能力的插件代码。
inject(第二节第 2 段):插件声明 Service 依赖的方式。依赖尚未就绪时,Cordis 不启动插件;依赖离开以后,相关组件也可以退出当前运行状态。
Capability Seam(第二节第 3、4 段):一项能力的完整替换边界。它把公开契约、能力实现和能力使用者放进同一条关系中,规定实现可以在哪里被换掉。
Service Definition(第二节第 3 段):Capability Seam 中公开的能力契约,规定 Provider 与 Consumer 共同遵守的接口和事件。
Provider(第二节第 3 段):按照 Service Definition 提供具体能力实现的组件。例如 fs-local、fs-sandbox 和 fs-e2b 都可以实现 ctx.fs。
Consumer(第二节第 3 段):通过 Service Definition 使用能力的组件。例如 tool-fs 只调用 ctx.fs,不直接绑定某个文件系统 Provider。
Typed Event(第二节第 5 段;第三节第 6、7 段继续分类):带有明确名称、类型和分发语义的事件接口,插件通过它交换状态或接入运行过程。
Reversible Effect(第二节第 6、7 段):需要随插件生命周期撤销的内部登记或资源。插件卸载或重载时,Cordis 调用对应的撤销动作,清除工具、监听器和 Provider 等内部状态;外部数据库或生产变更不在这一机制的回滚范围内。
Spatiotemporal Composability(第二节第 8 段):Cordis 对空间组合性与时间组合性的总称。前者处理组件在同一时刻怎样按照依赖建立关系,后者处理组件进入、替换和退出时,内部 Effect 怎样随生命周期清理。

Agent 的运行过程

Agent Loop(第三节第 1 段):发生在 Runtime 内部的任务循环,从一次输入开始,经过模型请求和工具调用,直到这一轮任务结束。
Turn(第三节第 2、3 段):从一项输入开始,直到系统不再有待处理工作的一轮运行。一个 Turn 可以包含零个或多个 Step。
Step(第三节第 2、3 段):一次模型请求,以及这次请求触发的工具调用。同一次模型响应产生多个工具调用时,它们仍然属于同一个 Step。
Tool Execution Pipeline(第三节第 4 段):Runtime 解析模型生成的工具调用以后,用来完成策略检查、审批、Provider 调用和结果回写的执行管线。
Event(第三节第 6、7 段):DeepSeek Harness 在不同组件和运行阶段之间传递事实或控制信号的基本形式。本文随后按照用途将它分为三类。
Session Event(第三节第 6、7 段):写入会话并需要跨重载保存的持久事实,例如用户输入、模型消息、工具调用和工具结果。
Agent Event(第三节第 6、7 段):服务当前 Agent 运行的实时控制事件,例如输入到达、状态变化、请求和停止,不以长期保存为首要目的。
Capability Event(第三节第 6、7 段):围绕某项具体能力调用发生的扩展事件,例如在工具执行前接入审批或参数改写。

会话记录与运行路径

Session Log(首次出现于开篇第 2 段;集中解释见“Session Log 保存模型看见过的运行事实”):用只追加事件保存会话历史的记录,也是 DeepSeek Harness 构造会话状态和后续模型输入的事实来源。
Model-visible means logged(第四节第 2 段):凡是会进入模型请求的内容,都必须能够从 Session Log 中的事件重建。插件不能让影响模型输入的内容只存在于自己的临时内存中。
Trajectory(第四节第 4 段):本文对运行历史面向人的路径视图所用的名称。构建者可以按 Step 查看模型请求和工具调用;本文没有把它视为与 Session Log 并列的另一套状态模型。
AI Workflow Owner(“每一项开放性都对应一条架构约束”第 3 段):理解专业工作流程,并对 Agent 的上下文、执行边界、审批位置、异常接管和持续修改负责的人。

文中的配置与包名实例

dsh-base、dsh-web-app、dsh-headless(第一节第 2、3 段):本文用于解释 Bundle 的官方实例,分别提供基础能力、浏览器应用和一次性任务执行器。
web、headless(第一节第 3 段):本文用于解释 Profile 的两个官方模板,前者组合 dsh-base 与 dsh-web-app,后者组合 dsh-base 与 dsh-headless。
ctx.fs与tool-fs(第二节第 3 段):文件能力实例中的 Service Definition 与 Consumer。tool-fs 通过 ctx.fs 发出文件操作请求。
fs-local、fs-sandbox、fs-e2b(第二节第 3 段):ctx.fs 的三个 Provider 实例,分别把文件操作交给本机、受控沙箱或远程环境。