docs: clarify DeepSeek Harness plugin composition

This commit is contained in:
wuyang6
2026-08-14 10:18:13 +08:00
parent 8a3d5e9694
commit a03dc0e1e4
@@ -43,6 +43,77 @@
这条链路的价值在于,扩展不必侵入一个巨大核心:插件可以提供服务、监听或包裹 waterfall,并在卸载时撤销注册。Cordis 预印本将其归纳为时间可组合性与空间可组合性:一方面追踪可逆 effect,另一方面根据依赖变化重新协调组件。但论文仍处于 active revision,适合作为设计解释,不应当作稳定标准。[Cordis 预印本](https://github.com/cordiverse/paper) 这条链路的价值在于,扩展不必侵入一个巨大核心:插件可以提供服务、监听或包裹 waterfall,并在卸载时撤销注册。Cordis 预印本将其归纳为时间可组合性与空间可组合性:一方面追踪可逆 effect,另一方面根据依赖变化重新协调组件。但论文仍处于 active revision,适合作为设计解释,不应当作稳定标准。[Cordis 预印本](https://github.com/cordiverse/paper)
## Cordis 插件树到底怎样实现
“插件树”容易让人误以为是一棵类继承树。dsh 里实际叠加了三层机制:
1. **配置层决定“挂载什么”。** Profile 先列出有序 Bundle;启动时再依次应用 Bundle patch、Profile 自身 patch、Harness home patch 和命令行 `--patch`。每个配置行用 `id` 作稳定身份,`name` 指向插件模块,还可带 `config`、`inject` 和 `disabled`。后层 patch 按 `id` 命中前层行时,替换整个 `config`,不做字段级深合并。[CLI 组合参考](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/apps/cli/reference/README.md)
2. **Cordis 运行时决定“何时激活”。** 根 `Context` 下的 Loader 把每行配置挂成一个插件实例,每个实例都有 Fiber 状态。插件通过 `apply(ctx, config)` 贡献能力,通过 `inject` 声明必需服务。因此 YAML 行的先后不决定加载顺序:依赖未就绪的插件保持 `PENDING`,服务出现后才激活。[Cordis 服务教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/03-services.zh.md)
3. **effect 层决定“怎样安全拆除”。** `ctx.tools.register(...)`、`ctx.on(...)`、服务注册和子插件都与当前插件生命周期绑定。配置删除、HMR 或服务提供方消失时,Cordis 会卸载相关 Fiber,反向撤销注册;服务恢复后,依赖它的插件再重新加载。[Cordis 生命周期教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/02-lifecycle-and-effects.zh.md)
可以把一个简化后的运行时理解为:
```text
Root Context
└─ Loader
├─ tools provider Fiber → 提供 ctx.tools
├─ greet-tool Fiber → inject: tools
├─ llm provider Fiber → 提供 ctx.llm
├─ agent-loop Fiber → 消费 agents / tools / llm
└─ surface Fiber → Web、ACP 或 SDK JSON-RPC
```
这棵“树”同时包含两种关系:Loader 建立的父子生命周期,以及 `inject` 建立的服务依赖图。Bundle 是生成配置行的发布层,并不会成为一个不可拆的运行时黑盒。
### 一个最小例子:向模型增加 `greet` 工具
下面是官方 Cordis 教程示例的精简版。插件不自己启动 Agent,只声明“我需要 `tools` 服务,并向它注册一个工具”:
```ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet',
description: 'Greet the named person.',
parameters: {
name: { type: 'string', required: true },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
execute: async ({ name }) => `Hello, ${name}!`,
}))
}
```
假设将它发布并安装为虚构示例包 `@acme/dsh-greet-tool`,可在 Profile 的 `cordis.patch.yml` 插入一行:
```yaml
- insert:
- id: greet-tool
name: '@acme/dsh-greet-tool'
```
这个例子假定所选 Profile 已有 `tools`、LLM 和 agent loop 等基础行;它只演示新能力如何插入既有 Agent。完整过程是:Loader 看到 `greet-tool` 行 → 发现它需要 `tools`,Fiber 在缺失时保持 `PENDING` → `ctx.tools` 就绪后调用 `apply` 并进入 `ACTIVE` → `greet` 的 schema 进入模型工具列表 → 模型发出 `greet({"name":"Ada"})` 时经标准工具管线执行 → 返回 `Hello, Ada!`。删掉这行或 HMR 热替换包时,Loader 根据稳定 `id` 重挂变化节点;旧 Fiber 进入卸载并最终 `DISPOSED`,工具注册随 effect 自动撤销。官方完整例子还演示了如何监听 `tools/result` 事件。[进入 Harness 教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/07-into-the-harness.zh.md)
### “接入协议”要先区分方向
| 接入面 | 连接方式 | 解决的问题 |
|---|---|---|
| Cordis 插件 | 进程内:TypeScript `apply(ctx, config)`、`inject`、Service、Event、effect | 增加或替换模型、工具、会话、策略、loop 和 UI 组件 |
| Profile / Bundle / patch | 部署时:YAML 配置行与稳定 `id` | 选哪些插件、如何覆盖默认值;它不是远程协议 |
| Web | 浏览器 → dsh:HTTP POST 一元调用;dsh → 浏览器:WebSocket 事件下行 | 产品 UI、会话展示和交互式操作;业务 Remote 由 Typert 生成严格描述 |
| ACP | 外部 Agent 客户端 ↔ dsh:Agent Client Protocol over newline-delimited JSON-RPC stdio | 标准化的 `session/new`、`session/prompt`、取消和一次性权限回答 |
| Python / TypeScript SDK | 评测器或程序 ↔ dsh:项目自有的逐行 JSON-RPC 2.0 stdio | 批量任务、自动化和 benchmark runner;方法为 `initialize`、`session/prompt`、`shutdown` 及事件/状态通知 |
| MCP client | dsh ↔ 外部工具服务器:MCP stdio 或 Streamable HTTP | 发现工具并注册为 `mcp__<server>__<tool>`;当前不桥接 Resources 和 Prompts |
关键区别是:**Web 是浏览器产品面;ACP 和 SDK 是“从外部驱动整个 Agent”;MCP 是“把外部工具接给 Agent”;Cordis 则是“在进程内改变 Agent 由哪些部件构成”。** 它们解决的问题不同,不应放在同一层比较。ACP 只暴露基线自动化能力,而 SDK 线协议当前没有版本协商和单 prompt 取消,都不能等同于 Web 产品面的完整功能。[ACP 协议契约](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/acp/acp/README.md) [SDK 线协议](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/sdk/protocol/README.md) [Web Connection](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/client/connection/README.md) [MCP client](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/mcp/mcp-client/README.md)
## 这套设计解决了什么 ## 这套设计解决了什么
### 运行时结构也可以替换 ### 运行时结构也可以替换