跳至正文
Agents — DeepSeek Harness 技术调研:为什么可以说「一切皆插件」

DeepSeek Harness 技术调研:为什么可以说「一切皆插件」

DeepSeek Harness 官网 · 官方仓库

AI 参与说明(Agent:Codex):本文由 Codex 使用 Context7 检索官方文档,结合固定版本源码、Cordis 运行实验与 Anatomy 交互图解整理,并使用项目 humanizer-zh 编辑中文正文。执行入口为 Codex Desktop;完整模型标识、reasoning effort 与运行时版本未取得可核验的任务级记录。

DeepSeek Harness(命令名 dsh)将 Agent Loop、模型适配、工具注册、Session、权限策略和 Web UI 都纳入同一套插件组合机制。“一切皆插件”的实质,是让产品的主要能力共享依赖声明、生命周期和扩展契约。工具插件只是其中一种消费者;驱动 Agent 的默认循环本身也可以被替换。架构说明

本文以 2026-10-27 取得的提交 22ddee379240 为准。根包版本是 0.2.1-rc.3,vendored Cordis 为 4.0.5;项目仍处于 developer preview,接口会发生不兼容变更。官网展示产品体验,以下机制结论以该提交的代码为依据。版本信息、Cordis 版本

英文术语中文名称简要解释
HarnessHarness,智能体运行框架管理模型调用之外的工具、上下文、会话、权限与界面
Plugin插件由运行时加载、拥有独立生命周期的一段能力实现
Context上下文插件获取服务、注册行为和持有资源的入口,即 ctx
Service服务以稳定名称提供的能力,例如 ctx.tools、ctx.web
Fiber插件运行实例同一插件每次挂载所创建的实例,记录状态、依赖与清理函数
Effect副作用注册工具、监听事件或打开连接等对环境的改变;这里强调可清理
Capability Seam能力接缝Service Definition、Service Provider 与 Consumer 之间的替换边界
Profile / Bundle配置档 / 组合包Profile 选择要运行的应用组合;Bundle 分发插件配置与代码
HMR热模块替换在允许的范围内卸载旧模块并加载新模块

从功能到代码:哪些东西成为了插件

The agent loop is a plugin, and the public Agent contract is separate from its default implementation. 扩展插件依赖 dsh-agent 的接口与事件;默认实现位于 dsh-agent-loop。这是判断插件化深度最直接的证据:能替换的范围包括执行任务的驱动层。Core 说明

能力主要代码入口,均相对于仓库根目录组合方式
Agent API 与默认循环packages/core/agent、packages/core/agent-loopctx.agents 提供公共入口,ctx.agentLoop 安装默认实现
模型调用与流式响应packages/llm/llm、packages/llm/llm-deepseek向 ctx.llm 注册 adapter,Loop 通过共同接口调用
工具 schema 与执行packages/core/tools、各组 tool-* 包向 ctx.tools 注册定义,执行经共享 pipeline
Shell 与运行环境packages/shell、packages/subprocess、packages/fs、packages/ssh消费能力接口,按组合选择本地或远端实现
Session 与持久化packages/core/session、packages/session/session-persistence*内存日志与落盘 backend 分开
System Prompt 与上下文packages/core/system-prompt、packages/context、packages/compaction注册 section、运行时事实及压缩策略
Skill、MCP 与 Subagentpackages/skill、packages/mcp、packages/subagent各有服务与面向模型的工具;接入共同注册和执行机制
权限与人工交互packages/sandbox、packages/interaction、packages/guard在工具、文件或进程的扩展点施加规则
Web UIpackages/client/modules、packages/client/ui-slots、packages/client/ui-rendererHost 发布模块图,Client 组合服务与 Slot
启动与插件管理packages/boot/app-boot、packages/boot/plugin-manager、packages/bundleProfile、配置 patch、安装管理及重载

这张表描述职责入口,不表示每个包都提供一个 ctx key。例如 core/scope 是不注册 Service 的底层库,Bundle 的主体可以是配置文件。项目把产品能力组织成插件,也保留普通类型、纯函数、协议和构建工具。包组织、Architecture

架构基础:一个 Context 同时管理依赖与资源

Cordis 解决两个方向的问题:一个插件需要哪些环境条件,以及它退出后怎样清理自己留下的东西。前者决定能否运行,后者决定能否安全替换。论文分别称为 spatial composability 与 temporal composability,并以 reactive coeffects 和 revertible effects 描述其运行机制。这里采用论文的概念解释实现,不将形式化结论外推为任意第三方插件都能正确回滚。Cordis 论文

flowchart TB
  P["Profile + Bundle + patches"] --> L["Loader:配置成为 Plugin tree"]
  L --> F["Fiber:一个 Plugin 的运行实例"]
  F --> C["Context"]
  C --> D["inject:需要哪些 Service"]
  C --> E["Effect:拥有何种注册与资源"]
  D --> R["Reflect:按隔离标识解析 Service"]
  R --> A["依赖满足才执行 apply"]
  A --> E
  E --> U["卸载时调用 disposer"]
  U --> R

上图是机制关系图,表示加载、依赖解析与资源回收之间的联系;不表示每条边都是异步函数调用。对应底层实现是 Context、RegistryService、ReflectService、Fiber 和 EventsService。Context、Registry

Plugin、Service 与 Fiber 分别是什么

一个 Plugin 可以是接收 (ctx, config) 的函数、带 apply() 的对象,或可构造的类。ctx.plugin(plugin, config) 将这些形式标准化并创建 Fiber。同一段插件代码可以挂载多次,每次都有自己的配置、状态和生命周期。Service 子类也是一种 Plugin;super(ctx, 'name') 让它在 Context 中占据一个稳定名称。Plugin 类型、Service

以 ctx.web 为例,消费者只要求这个名字所代表的接口。TypeScript 的 declaration merging 让 ctx.web 获得静态类型;真正的运行时注册由 provide() 或 Service 构造完成。类型声明本身不会加载插件,也不会创建依赖。Services 教程

inject 是持续有效的条件

Required service dependencies remain reactive after startup. inject 并非启动时查一次的清单:依赖发生变化后,运行时会重新判断 consumer 是否还能保持激活。Services 教程

源码中的关键步骤是:

  1. ReflectService.provide() 保存带有 name、value 和提供方 fiber 的记录;服务可用性发生变化时调用 notify()。
  2. 依赖方的 Fiber._checkImpl() 重新解析需要的实现;_refresh() 用依赖提供方的 fiber.uid 形成 epoch。
  3. 任一依赖缺失时,epoch 成为 inactive;即使名称相同,提供方 Fiber 变了也会使 epoch 改变。
  4. _setEpoch() 据此安排卸载或加载;重新加载时,apply() 取得当前依赖,旧的注册先完成回收。

因此,配置中的行顺序不需要承担所有启动排序。consumer 写在 provider 前面时,可以先停在 PENDING,待服务可用再加载;循环依赖或缺失配置也可能让它一直等待,需要用诊断定位。Reflect 源码、Fiber 源码

Effect 把“注册”变成可撤销的操作

ctx.on() 注册监听器,ctx.plugin() 挂载子插件,ctx.provide() 提供服务,Harness 的 ctx.tools.register() 注册工具,都把清理责任归到调用方生命周期。插件卸载后,这些注册随之移除。自行创建的 timer、连接或 watcher 则要用 ctx.effect() 返回 disposer,运行时无法替插件猜出如何关闭它们。生命周期教程、ToolsService.register

这里撤销的是运行环境中的注册与资源持有关系。插件已经发出的邮件、写入的业务数据或完成的网络操作,不会因为卸载而自动恢复原状。需要补偿的外部操作必须另行设计。

清理顺序也有边界:Fiber 卸载会等待异步清理,但多个独立 disposer 可并发运行;同一个 ctx.effect() 内收集的清理按逆序串接。若必须先停生产者、再排空队列、最后关连接,应在一个 disposer 内明确 await 这个顺序。Fiber.effect 与 _unload

Anatomy:服务退出与 provider 退出为何不同

这张图解针对真实的 tool-web 插件。它声明 inject = ['tools', 'web', 'systemPrompt'],按配置注册 web_search、web_fetch。图中假设两个工具都启用,另外两个 Service 持续可用;后排插座表示依赖,中间电路板表示 consumer,前排两枚插片表示这个 consumer 拥有的工具注册。动作表现依赖与注册的连接、退出和重新建立。tool-web 源码

ANATOMY / CORDIS PLUGIN LIFETIME

拔掉服务,谁的注册会跟着退出?

后排三个插接点对应 inject;中间电路板对应 tool-web;前排两枚插片对应它拥有的工具注册。最前方为不依赖 web 的独立插件。

Fig. 1 · required services → owned registrationsACTIVE
后排:tools / web / systemPrompt中排:tool-web → search / fetch前排:独立插件,保持注册
2 个 Web 工具定义tools ✓ · web ✓ · systemPrompt ✓HTTP fetch provider:存在可执行(假设 provider 已配置)
0%

1 / 三个必需服务均存在,tool-web 的两个注册已插入 registry。

教学慢放 · 空闲时自动演示;播放后可暂停、慢放或拖动进度。运动时间不代表真实执行耗时。

服务与 provider 是两层:中间按钮保留 ctx.web,因此板和两枚插片都不退出;fetch 执行才会报告 provider 不可用。抬升距离和动画时长用于教学,不表示源码中的性能或真实硬件。

教学模型固定另外两个服务可用,两个工具配置均启用;省略 prompt 注册与进行中的调用。服务恢复会重新建立注册,不能推导为自动恢复网络请求。依据 Fiber 生命周期 与 tool-web;绘图使用项目 Anatomy kit。

“移除 ctx.web”让 consumer 缺少必需 Service,清理完成后两个工具定义退出 registry。“只移除 HTTP provider”保留 ctx.web,工具插件继续运行,工具定义也保留;调用 web_fetch 时会因选中的 provider 不可用而失败。这两种变化不能画成同一种卸载。web-fetch-http、Web 能力

图解使用依据源码重建的教学模型,不在浏览器运行完整 dsh。依赖移除与恢复的状态规则已用该版本真实 Cordis 验证;时间和抬升距离只为观察而设定。图中省略 System Prompt 注册、进行中的调用以及异步清理失败,不能据此推断它们的取消或恢复保证。

一个需要以源码校正文档的状态细节

当前用户文档将依赖丢失概括为 ACTIVE → DISPOSED。但固定版本源码的 _getState() 在 uid 未清空、epoch 已 inactive 时返回 PENDING。本次实验确认:移除 provider 后 consumer 保留同一个 uid,清理资源并回到 PENDING;新的 provider 出现后,这个 consumer 再次加载。显式调用 consumer 的 dispose() 才清空它的运行身份并进入 DISPOSED,之后不会因服务重现自行恢复。用户文档、源码

stateDiagram-v2
  [*] --> PENDING
  PENDING --> LOADING: 必需 Service 齐全
  LOADING --> ACTIVE: apply 完成
  LOADING --> FAILED: 配置或 apply 失败
  ACTIVE --> UNLOADING: 必需 Service 丢失或改变
  UNLOADING --> PENDING: 依赖仍缺失且实例保留
  UNLOADING --> LOADING: 新依赖已就绪
  ACTIVE --> UNLOADING: 显式 dispose
  UNLOADING --> DISPOSED: 显式移除并清理完成

这张图突出两种卸载原因,不穷举失败、加载竞争和父插件退出的所有转移。排障时要同时观察 Fiber 状态、实例身份与实际注册,不能只看插件配置是否还存在。

Capability Seam:接口、实现和模型工具分开

以网页访问为例,dsh-web 提供 Service Definition 与 provider registry;dsh-web-fetch-http 注册 HTTP fetch 实现;dsh-tool-web 作为 Consumer 定义模型能看到的名称、参数和输出。替换 HTTP 实现时,模型仍可使用同一个 web_fetch 工具接口。Web subsystem

flowchart TB
  T["Consumer: dsh-tool-web<br/>web_search / web_fetch"] --> W["Service Definition: dsh-web<br/>ctx.web"]
  H["Service Provider: web-fetch-http"] -->|注册 fetch 实现| W
  S["Service Provider: web-search-deepseek"] -->|注册 search 实现| W
  W -->|按配置选择实现| H
  W -->|按配置选择实现| S

图中 provider 的注册与调用方向相反:启动时向 registry 提供实现,执行时由 Service 选择实现。它们都是 Plugin,但不全都各占一个 Service 名称。ctx.llm 的 adapter registry 也有类似分层;因此“换 provider”既可能是替换整个 Service 实现,也可能只是修改同一 Service 内注册的一项能力,应按具体接口判断。Capability 定义、LLM streaming

这种拆分把模型协议、基础能力和具体环境分开。以 Shell 为例,Bash 工具不应直接决定在本地 spawn() 还是通过 SSH 执行;filesystem 与 subprocess 必须指向一致的执行环境,才能让 Bash、PTY 和 LSP 对同一套文件工作。替换边界成立的前提是各实现遵守共同契约。Architecture

一次请求如何穿过这些插件

假设用户要求获取一个网页,模型决定调用 web_fetch。默认实现中的主要数据对象包括:

对象关键含义代码位置
Agent活跃运行句柄、输入队列、状态与所属 ctxpackages/core/agent
Session / SessionEvent模型历史的事实来源,记录 turn、message、tool 等事件packages/core/session
Message / ContentBlockuser、assistant、tool-call 等模型交互内容packages/llm/llm
PreparedLlmCall绑定选定 adapter 及本次调用能力packages/llm/llm
ToolDefinition参数、执行函数、输出 schema 与渲染规则packages/core/tools/src/contracts.ts
ScopeKey用对象身份区分某个 Agent 的注册范围packages/core/scope

实际主线是 ReactLoopAgent 的 step 准备与执行,以及它对 ctx.tools.execute() 的调用。下图省略重试、压缩和取消分支,但保留模型上下文、工具执行与日志之间的先后关系。Agent 实现、Core 文档

sequenceDiagram
  participant A as Agent Loop
  participant P as System Prompt
  participant L as LLM adapter
  participant T as Tool executor
  participant W as Web Service
  participant S as Session
  A->>S: 打开 turn,领取输入
  A->>P: 组装 prompt sections 与本 step 工具
  A->>A: agent/pre-step 决定是否接纳输入
  A->>L: agent/request 后 prepareCall
  A->>S: 协调 system/message,提交 user/message 和 request 信息
  A->>L: 从日志导出历史,stream 已绑定的请求
  L-->>A: assistant 内容与 tool-call
  A->>S: 提交 assistant/message
  A->>T: ctx.tools.execute
  T->>S: tool/call
  T->>T: pre-execute → guards → execute
  T->>W: web_fetch 的执行函数调用 ctx.web
  W-->>T: 选定 provider 返回结果
  T->>T: 输出投影、post-execute、最终整理
  T->>S: tool/result
  A->>S: 后续 step 从日志导出包含结果的新历史

Model-visible information must be reconstructable from the Session log. Loop 从日志派生模型请求;插件新增模型可见信息也要有对应的 Session 事实。持久化让换 UI、恢复会话、导出轨迹时仍能读取共同记录,但这不等于重放日志会重新执行已经完成的外部工具。Session 设计

插件可以在 agent/pre-step 改变输入接纳,在工具 pipeline 施加权限或结果处理,在 llm/stream 接入模型传输。扩展沿明确的契约进入,避免每个新功能都修改 ReactLoopAgent。agent/request 在此版本已标为 deprecated,适合了解历史链路,新扩展要按当前公开接口选择切入点。Turn flow

Events 不只负责通知,也承载控制流

Cordis 的 waterfall 是带 next() 的 around middleware。一个监听器可以先做工作、await next()、再处理返回值;有权作决定的监听器也可以不调用 next(),直接短路。忘记调用 next() 会让下游根本没有执行。Waterfall 语义

工具 pipeline 的顺序是 tools/pre-execute、monotonic guards、tools/execute、tools/post-execute,输出投影与最终结果整理也各有位置。权限检查并不只依赖一个可随意替换的监听器:guards 在 pre-execute 之后约束执行,具体文件修改和进程运行还有各自的能力边界。Tool pipeline

其他 dispatch mode 也有明确语义:emit 同步广播且不等待返回的 Promise;parallel 等待并行监听器;serial 依序等待并在首个有效返回值处停止;bail 是同步的短路分派。不能把 serial 理解为保证执行所有异步监听器。Events 源码

多 Agent 如何避免注册互相污染

这里有两种不同的范围机制。

ctx.isolate('name') 给某个 Service 名称建立新的解析标识,使不同子树可以分别提供同名 Service。Context 以原型继承元信息,服务查找再按 isolation label 解析。同一个 label 可以让两处配置共享服务范围。Context.isolate

Harness 的 ScopeKey 则用于同一个 registry 内的按 Agent 注册。默认 Loop 用活跃 Agent 对象作为 scope key;通过 agent.ctx 注册的工具只在对应范围可见,并可遮蔽同名全局工具。全局与同层重复名称会受检查,局部注册退出后不会删除另一个 Agent 的同名注册。Scope、Tools registry

另一个容易混淆的 API 是 ctx.intercept(name, config):它建立带有 Service 专属配置的子 Context,由 Service 的配置合并机制读取。它不等于拦截所有方法调用;实际请求和工具执行的控制流由前文的 Events 与 waterfall 承担。Context.intercept、Service 配置合并

前者分开 Service 的实例解析,后者分开 registry 的贡献与生命周期。它们都不是操作系统沙箱:拥有 Node.js 执行能力的插件仍需受信任,文件和进程的安全性依赖实际采用的 sandbox 与权限方案。Safety notice

Web UI 也通过插件组合

Host 与浏览器各有自己的 Context,跨两端靠模块图、Remote 接口和事件通信。Client 不是直接持有 Node.js 端的同一个 ctx 对象。Web Client architecture

Host 的 ctx.clientModules 生成 Client 模块图;页面 boot 时取得插件入口和共享依赖信息。执行 bundle 首先注册 factory,模块主体在 materialization 时执行,结果进入缓存;CSS 等副作用也跟随模块的生命周期。开启或关闭插件后,页面跟随 Host 模块图更新相应 Loader entry,并等待异步清理。Client modules

UI 组合由 ctx.slots 承担。插件向声明过的 Slot 注册组件,父级只获得其声明范围内的渲染权限;注销入口时,子 Slot、贡献与 Store mount 一并退出。ui-renderer 把这套 React-free 的对象层连接到 React,是统一的渲染适配层。ui-slots、ui-renderer

因此,一个插件可以同时贡献模型工具、后端服务、设置界面和对话展示。官网“创造模式”里的番茄时钟展示了对话生成插件代码、安装 Bundle 和扩展界面的产品入口;架构上的依据则是这些具体注册点。官网演示

前端重载也有版本边界:页面同时核对 Host release 的 version 与 commit;Host 换成另一个 release 时,当前页面暂停应用它的新模块图,保留旧插件,等待整页刷新加载另一 release。不能把开发时局部 HMR 说成任意跨版本的无感升级。Client modules:Host release changes

Profile、Bundle 和 HMR 如何连接安装与运行

安装包、选择组合、运行激活是不同阶段。Profile 是 Harness home 中的命名应用组合;Bundle 用 package.json 的 dsh.bundle 声明配置 patch 与代码。启动时从空列表开始,依次应用 Profile 选中的 Bundle、Profile patch、home patch,最后应用命令行 --patch。Profiles and bundles

web、headless、sdk、acp 共享 dsh-base,再叠加各自入口;sdk-minimal 用独立 Bundle 声明完整树。这个例外说明“默认产品组合”也可被绕开选择另一套支持的组合,但应用仍通过统一 dsh launcher 启动。sdk-minimal 配置

HMR 由插件及配置决定。该版本 base 默认打开 config-only HMR,headless、SDK 和 ACP 默认关闭,sdk-minimal 不包含它。源码变化能否热替换,还取决于模块是否处于 Loader 管理的依赖图以及是否进入 Host 的静态导入图。Architecture、HMR API

实际更新大致经过:解析新的 Profile 组合、比较模块、停止受影响的 Loader entry、发布新的解析结果、重新导入并激活 entry。runExclusive() 将自动重载和插件管理变更串接,防止同时替换同一组合。无法安全替换的模块会报告 restart reason;替换插件启动失败时也可能留下 inactive entry,而不是自动恢复旧代码。HMR.reconcileProfile

排障时分别看三件事:包是否安装、组合中是否启用、Fiber 是否 ACTIVE。Plugin Manager 的 changed 只表示磁盘编辑,application 还会区分 applied、restart-required、overridden 和 failed,不能把一次安装成功等同于能力已经生效。Profile management

最小实验:直接验证服务替换与资源回收

下面是 Cordis 运行时实验,不调用模型,也不启动 Harness 应用。代码用一个 Set 模拟 registry,验证 consumer 失去依赖后清理资源、保留 Fiber,并在新 provider 出现后重新注册。真实工具 registry 用同样的生命周期机制,但其 schema、执行和 scope 规则未在此模拟。

为避免构建整个 monorepo,可在固定提交根目录保存下列两个文件;环境需要 Node.js 24.9+。临时安装的 esbuild 只用于把该提交的 TypeScript 源码打成可执行 ESM,不修改仓库依赖。

js
// lifecycle-smoke.ts
import {Context, FiberState} from './vendor/cordis/src/index.ts';
import assert from 'node:assert/strict';

const root = new Context();
const live = new Set();
const trace = [];
const provider = version => ({
  name: 'provider-' + version,
  apply(ctx) { ctx.provide('researchService', {version}); },
});
const consumer = root.plugin({
  name: 'consumer', inject: ['researchService'],
  apply(ctx) {
    const version = ctx.researchService.version;
    ctx.effect(() => {
      live.add(version); trace.push('load:' + version);
      return () => {live.delete(version); trace.push('clean:' + version);};
    });
  },
});
await consumer;
assert.equal(consumer.state, FiberState.PENDING);
const a = await root.plugin(provider('A'));
await consumer.await();
assert.deepEqual([...live], ['A']);
const uid = consumer.uid;
await a.dispose(); await consumer.await();
assert.equal(consumer.state, FiberState.PENDING);
assert.equal(consumer.uid, uid);
assert.equal(live.size, 0);
const b = await root.plugin(provider('B'));
await consumer.await();
assert.deepEqual([...live], ['B']);
await consumer.dispose();
assert.equal(consumer.state, FiberState.DISPOSED);
assert.equal(live.size, 0);
await b.dispose();
await root.fiber.dispose();
console.log(trace);
js
// build-smoke.mjs
import {build} from 'esbuild';
import {resolve} from 'node:path';
await build({
  entryPoints: ['lifecycle-smoke.ts'],
  bundle: true, platform: 'node', format: 'esm',
  outfile: 'lifecycle-smoke.mjs',
  alias: {
    '@deepseek-ai/cosmokit': resolve('vendor/cosmokit/src/index.ts'),
  },
});
sh
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
git checkout 22ddee3792402c3e70ec5f86a63c3e3e895a2f60
# 在这里保存上面的两个文件,再执行:
npm install --prefix /tmp/dsh-research-build --no-save esbuild@0.28.2
# 将构建脚本复制到临时工具目录,让其解析到该目录的 esbuild;cwd 仍为仓库根目录。
cp build-smoke.mjs /tmp/dsh-research-build/build-smoke.mjs
node /tmp/dsh-research-build/build-smoke.mjs
node lifecycle-smoke.mjs

本次使用已有 esbuild 0.28.2 对同一固定源码构建并运行上述生命周期代码,得到:

text
load:A → clean:A → load:B → clean:B
依赖消失:PENDING;consumer uid 不变;registry 为空
显式 dispose:DISPOSED;registry 为空

另外两项运行实验也通过:两个 isolate('researchService') 子树能持有不同实现,移除 A 不影响 B;waterfall 的外层包装会在 next() 返回后执行,而短路分支不会执行底层函数。这些结果验证插件运行机制,不构成完整桌面应用或真实模型调用的验收。

设计取舍与可借鉴的做法

“一切皆插件”最有价值的部分,是把替换能力所需的几项约束一并设计出来:稳定接口决定替换什么,reactive dependencies 决定何时能运行,Effect ownership 决定退出时清理什么,Session 契约决定换实现后还保留哪些事实。缺少其中一环,插件就容易退化为可以加载、难以移除的模块集合。

这套设计也增加了运行时排障成本。能力缺失可能来自未安装、配置被上层覆盖、服务依赖未满足、provider 尚未可用、scope 过滤或 Client 模块同步失败,单看 import graph 很难定位。官方 Inspector、Fiber 状态、有效配置和 Session trace 因此是架构可用性的一部分。Profile management、Client modules

若要在自己的 Harness 中采用类似方案,可以按以下顺序验证边界:

场景具体操作验收依据
替换执行环境先抽出 filesystem 与 subprocess 接口,再接入第二个实现同一工具 Consumer 无改动即可运行,且路径和进程处于同一环境
动态启停插件为每种注册返回 disposer,反复加载、卸载再加载无重复工具、残留监听器或未关闭连接;异步清理能被等待
替换必需服务先卸载提供方,观察依赖方,再挂载新实现consumer 在缺失期间不保持激活,恢复后重新绑定新实现
多 Agent 定制通过 Agent scope 注册同名局部工具A 的覆盖和退出不会改变 B 的工具视图
上下文可追溯为新增模型可见内容定义日志事件或投影能从 Session 重建实际请求,包含工具结果与必要的运行时事实
扩展 Web UI先声明 Slot 与 props,再注册组件与清理关闭插件后 UI、子 Slot 和订阅一并退出,旧闭包不能继续越权渲染

本文的判断是:DeepSeek Harness 的插件化深度来自 Cordis 提供的统一运行约束,再由 dsh 将这些约束贯彻到 Agent、能力层和界面。固定下来的仍包括 Context 解析、生命周期、事件语义、数据协议与启动契约;可组合的是遵守这些契约的实现。理解这个边界,才能既看见它比“多装几个工具”更深的设计,也准确评估重载、兼容性与安全方面的成本。

继续阅读

博客助手

正在打开博客助手…