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

上一篇谈到，DeepSeek Harness 的复杂并不完全来自 Developer Preview 阶段的粗糙。它把 Harness 的许多内部选择开放给构建者，目标是让构建者控制 Agent 怎样被组织和运行。这篇继续向架构内部走一步：当 **Everything is a Plugin** 成为整个系统的前提，DeepSeek Harness 用什么办法避免插件化变成一团混乱？

DeepSeek Harness 是一套 Agent Runtime。模型在其中生成文本或结构化调用，Runtime 负责组织模型收到的内容、调度工具并保存任务状态。Session Log用只追加的事件记录一次会话。按照官方架构文档，负责接入模型的适配器、工具系统、会话机制和任务循环都由插件提供，产品层没有一组永远不可替换的特权组件。[DeepSeek Harness Architecture](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md)

开放到这个程度，系统首先要确定一次启动加载哪些插件。选出的插件进入运行期以后，又要取得依赖、替换实现并清理自己留下的状态。当工具、上下文和任务循环都能被插件改变，已经发生的过程也需要成为可以重建的记录。这些问题会依次带出后面的术语，沿着装配、运行和记录的顺序连成一条设计链。

## 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；Profile 这一层本身没有把任务限定在代码仓库里。

**Patch** 处理一套组合在具体部署中的局部差异。加载顺序依次是 Profile 列出的 Bundle、Profile 自带的 Patch、用户目录 Patch 和命令行 `--patch`，后一层可以覆盖前一层。Patch 以配置行的 `id` 为目标，覆盖时替换该行的整份配置，也可以插入新行。例如，一个团队沿用 `web` Profile，却要改变其中的沙箱设置，就可以覆盖对应配置行，无须复制和修改 `dsh-base` 或 `dsh-web-app`。[DeepSeek Harness Architecture](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md)

Bundle 只有进入 Profile，才成为本次运行采用的一层；Patch 作用在已经选定的层之上。一个 Bundle 可以被多个 Profile 复用，同一个 Profile 也能在不同部署中保留少量配置差异。解析完成以后，这些选择形成一棵有挂载关系的插件树。Standard Mode 是其中一种已有装配结果；换一套 Profile，DeepSeek Harness 可以呈现为另一种 Agent。

插件树只解决了“本次启动有哪些组件”。它还没有回答插件进入运行期以后怎样找到依赖，以及某个实现被替换或卸载时，系统怎样避免留下失效的监听器、工具和服务实现。DeepSeek Harness 把这部分运行期关系交给 Cordis 管理。

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

**Cordis Context** 是插件共享的服务容器，也是插件获得生命周期的运行环境。插件挂载到 Context 以后，可以从稳定的键取得其他能力，并把自己的能力登记进去；插件退出时，Context 又限定哪些登记需要随它一起撤销。[Cordis Primer](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-primer.zh.md)

Context 中供其他插件使用的能力叫作 **Service**。一项 Service 占据稳定的 `ctx.<key>`，使用者依赖这个键，而不必直接导入某个具体实现。插件通过 `**inject**` 声明自己需要哪些 Service。比如一个插件需要会话服务，它先在 `inject` 中写明这项依赖；会话 Service 尚未就绪时，该插件不会启动。依赖离开以后，Cordis 也能让相关组件退出当前运行状态。Cordis 所说的空间组合性就发生在这里。组件声明自己需要什么，运行系统根据当下存在的依赖建立关系，无需每个插件自行猜测加载顺序。

仅有稳定的 Service 名称，还不足以保证某项能力可以替换。DeepSeek Harness 用 **Capability Seam** 描述一项能力的完整替换边界。**Service Definition** 是公开的能力契约，规定接口和事件；**Provider** 实现这份契约，**Consumer** 只通过契约调用能力。以执行环境为例，Consumer 可以请求运行命令，Provider 可以把请求交给本机、容器或远程沙箱，调用逻辑不必绑定某个环境。[Capability Seams](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/capability-seams.zh.md)

Profile 完成启动时的组合选择以后，Capability Seam 接着规定一个实现要在什么接口边界内被换掉。Provider 的替换成本取决于接口是否完整，也取决于 Consumer 有没有绕过接口依赖内部细节，这些问题要到代码走读和案例中检验；概念本身先把“可以换”落实成了接口、实现与使用者之间的关系。

插件之间还需要通信和拦截运行过程。**Typed Event** 是带有明确名称、类型和分发语义的事件接口。它给扩展行为规定可识别的入口，插件可以通过事件交换状态，也可以在指定流程节点接入策略。到 Agent Loop 部分，同一套事件机制还会承担另一项工作：把需要长期保存的事实与只服务当前运行的控制信号分开。

动态系统最麻烦的地方通常发生在退出时。插件注册了一项工具、一个 Provider 或一段事件监听，如果卸载只删除插件对象，这些登记仍可能留在运行环境里。Cordis 把这类受 Runtime 管理的注册称为 **Reversible Effect**。注册发生时会留下对应的撤销动作，插件被卸载或重载时，Cordis 调用这些动作，清除该插件在当前作用域中登记的内部状态。[Cordis Lifecycle and Effects](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-tutorial/02-lifecycle-and-effects.md)

Reversible Effect 的作用范围是 Cordis 管理的内部登记，包括工具、监听器和 Provider。已经提交的数据库变更与生产环境中的其他操作位于这个范围之外，需要事务、补偿动作或人工处置。插件系统能否重载和替换，很大程度上取决于旧组件退出后会不会继续污染新的运行状态，所以内部 Effect 的撤销依然很重要。

Cordis 用 **Spatiotemporal Composability** 概括这套设计。空间组合性管理组件在同一时刻怎样按照依赖建立关系，时间组合性处理组件进入、替换和退出以后，内部 Effect 怎样随生命周期撤销。Cordis 的论文正在持续修订，本文只使用它对依赖关系与生命周期的解释，形式化理论留给后续专题。[Cordis Paper](https://github.com/cordiverse/paper)

Profile 和 Bundle 让插件树能够被装配出来，Cordis 则让这棵树在运行中保持关系。Agent 接到任务以后，还需要一套所有组合都能遵守的工作节奏，否则每种 Profile 都可能发明自己的模型调用、工具执行和结束条件。

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

**Agent Loop** 是驱动 Agent 从一次输入走向模型请求、工具调用、下一步或结束的运行循环。它本身可以由插件提供，但只要接入 DeepSeek Harness，就要使用 Runtime 能够识别的任务边界和事件协议。[Agent Lifecycle](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/agent-lifecycle.zh.md)

官方架构用 **Turn** 和 **Step** 划分这段过程。Turn 是从一项输入开始，直到系统不再欠下待处理工作的一轮运行，它可以包含零个或多个 Step。Step 是一次模型请求，以及这次请求触发的工具调用。Turn 回答一轮任务何时结束，Step 给模型调用和随后发生的工具执行建立可定位的单位。

把前面的概念放进一次运行，整套关系就会清楚许多。Profile 先选出 Bundle 与 Patch，Cordis 把相应插件挂载到 Context，插件通过 Service 取得依赖，并注册工具或 Provider。用户输入到达 Agent 后，Agent Loop 开启一个 Turn；进入 Step 时，Runtime 从会话历史组装模型需要的消息和工具定义。模型返回普通文本或结构化工具调用，Runtime 解析调用并送入工具执行管线。管线在执行前接入策略检查，规则要求审批时会暂停执行并等待批准，随后由对应 Provider 完成调用，工具结果进入会话。[Tool Execution Pipeline](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/tool-execution-pipeline.zh.md)

模型生成调用，Runtime 解析和调度，Provider 执行工具，人的批准只发生在规则要求的节点。把这些动作分开，才能看清 Harness 承担的工程工作。Agent Loop 根据结果判断当前 Turn 是否仍有待处理工作；需要模型继续处理工具结果时，它开启下一个 Step，没有待处理工作时，本轮结束。本文只走最短的主路径，错误恢复和全部停止分支留给后面的代码走读。

这一过程中会经过几类事件。**Session Event** 是写入会话、需要跨重载保存的持久事实，例如输入、模型消息、工具调用和工具结果。**Agent Event** 服务正在运行的 Agent，处理输入到达、状态变化、请求或停止等实时控制，不以长期保存为首要目的。**Capability Event** 则把策略和适配器接到某项具体能力的调用过程中，例如工具执行前的审批与改写。

前一节的 Typed Event 主要解释插件怎样通信；到了 Agent Loop，它增加了一层更严格的区分。影响会话历史的内容要成为 Session Event，当前运行中的协调交给 Agent Event，能力调用周围的扩展由 Capability Event 接入。插件仍然可以改变运行方式，但它必须选择相应的事件域，不能把持久事实和临时控制混成同一种消息。

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

用只追加的事件记录会话，是 Session Log 的第一层作用。DeepSeek Harness 同时把它作为会话状态的事实来源，模型消息历史由这条事件流派生，分叉、恢复、转录、遥测和持久化也可以围绕同一历史建立。[Session README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md)

官方架构给出了一句直接的约束：**Model-visible means logged**。任何会进入模型请求的内容，都要能够从 Session Log 中的事件重建。一个插件可以给模型增加提示词片段、工具结果或其他上下文，但这些内容一旦影响模型下一步看到的输入，就不能只存在于某个插件的临时内存里。新的模型请求要从已有事件派生，生成的消息、工具调用与结果继续写回同一条事件流。[DeepSeek Harness Architecture](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md)

Session Log 因而同时处在两种关系里。面向构建者，它保留一次任务中可检查的输入、输出、调用和状态变化；面向 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 面向的首先是负责设计和维护工作流的人，这一点与上一篇的判断保持一致。

我目前愿意给这套概念设计很高的评价，因为插件的自由从启动装配一直延伸到运行历史，每一层又受到下一层的约束。这份评价只针对概念设计。项目仍处于 Developer Preview，Cordis 的理论工作也在修订；接口在版本变化中能否保持清楚，插件的动态组合会增加多少调试成本，Session Log 能否在故障复盘中帮助人定位问题，都要等使用教程、代码走读和案例篇拿出材料。[DeepSeek Harness Repository](https://github.com/deepseek-ai/deepseek-harness)

这一篇解释了 DeepSeek Harness 的架构怎样安排这些概念。下一篇开始使用 Standard Mode，我们会第一次从产品表面进入这些抽象，看一次任务怎样启动、调用工具、等待审批，并把运行历史留在 Session 中。