乐于分享
好东西不私藏

插件到底是个什么鬼?——DeepSeek Harness 插件五层拆解

插件到底是个什么鬼?——DeepSeek Harness 插件五层拆解

从"插件就是个 App"到"插件是一段可逆副作用的代码",五层台阶,带你从零走到能自己写。

8 月 13 日 DeepSeek Harness(`dsh`)开源,Hacker News 冲上第一,Star 几天逼近 40k。几乎每一篇解读都提到同一句话:**"一切皆插件"**。

但这句话对刚接触的人,像一句咒语。插件到底是什么?是 App?是配置文件?是一段代码?为什么官方文档敢说"连 Agent Loop 本身都是插件"?

这篇文章把"插件"拆成五层台阶,一层一层往上爬:

- **第一层**:插件 = 给 AI 装的 App(零基础视角)

- **第二层**:插件 = 带开关和参数的配置(用户视角)

- **第三层**:插件 = 一段注册了东西的代码(开发者视角)

- **第四层**:插件 = 可逆副作用(架构视角)

- **第五层**:插件 = 让 AI 自己给自己装插件(终极形态)

每层都配一个**真实可跑**的例子。走完五层,你不仅能装插件,还能动手写一个。

---

## 第一层:插件 = 给 AI 装的 App

**先想一个场景:**

你的手机出厂自带相机、电话、短信。但要打车、点外卖、P 图,你得去应用商店装 App——不用重新买手机,也不用找厂商要系统更新。

dsh 里的插件,就是给 AI 装的 App。

**真实例子:让 DeepSeek 学会"看"图**

DeepSeek 当前这条文本路由是纯文本模型,它本身**看不懂图片**。但你给它装一个叫 DSH Vision Toolkit 的插件,它就"长出眼睛"了——图片问答、OCR、定位图中元素、裁剪对比、HTML 截图,全都能干:

```sh

dsh plugin --profile web add @dsh-external/dsh-vision-toolkit

```

装完之后重启 profile,在会话里加载插件自带的 Skill,然后直接说:

> 请用 vision_glance 分析 ./screenshot.png,告诉我页面上出现了什么错误。

模型就会调用插件暴露的 `vision_glance` 工具,真的去分析图片,再回答你。

**这一层你只需要记住**:插件 = 给 AI 加新本事的东西,装了就有的那种。不用重造 AI,不用动核心,像装 App 一样。

---

## 第二层:插件 = 带开关和参数的配置

如果插件只是"装/卸",那和 App 没区别。但 dsh 的插件比 App 多一样东西:**你可以像拧螺丝一样调整它的行为**。

**真实例子:在界面上加一个"+"号**

有一个社区插件叫 `dsh-provider-quick-config`。默认的 dsh 只接 DeepSeek 官方模型,装上这个插件之后,发送键旁边会多一个 **+**,点开就能可视化添加其他模型厂商——智谱 GLM、MiniMax、OpenAI、Claude、本地 Ollama,端点、协议、推理格式、模型列表全部预填好,填个 API Key 就能用,不用改任何配置文件:

```sh

dsh plugin --profile web add file:/path/to/dsh-provider-quick-config-0.1.2.tgz

```

这还不是全部。dsh 默认部署了 **159 个插件**,`llm`、`session`、热重载插件和其他插件平级躺在设置面板里,**每一个都能单独开关**。而每个插件还能带自己的配置:

```yaml

- id: tool-bash

name: '@deepseek-ai/dsh-tool-bash'

config:

enableRunInBackground: true # 允许后台运行长命令

```

这是真实存在的配置项——`tool-bash` 插件的源码里就声明了它:

```ts

/** Configuration for the bash tool. */

export interface Config {

/** Expose `run_in_background` (default true); disabled calls are also rejected. */

enableRunInBackground?: boolean

}

```

**这一层你只需要记住**:插件不只是"装不装",还有"怎么调"。装 App 只是第一步,拧配置螺丝才是日常。

---

## 第三层:插件 = 一段注册了东西的代码

前两层都是"用",从这一层开始"写"。好消息是:**dsh 官方的第一个插件教程,总共就十几行代码。**

**真实例子:官方 greet 工具**

在 `scratch-plugin/src/my-plugin.ts` 里写:

```ts

import type { Context } from '@deepseek-ai/cordis'

import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'

export const inject = ['tools']

export function apply(ctx: Context) {

ctx.tools.register(defineTool({

name: 'greet',

description: 'Greet someone by name.',

parameters: {

name: { type: 'string', required: true, description: 'The name to greet' },

},

output: {

schema: { type: 'string' },

render: (_args, value) => [{ type: 'text', text: value }],

},

async execute(args) {

return `Hello, ${args.name}!`

},

}))

}

```

然后带一个声明文件启动:

```sh

pnpm dsh web --patch ./scratch-plugin/cordis.yml

```

打开 `http://127.0.0.1:3080`,输入:

> Use the greet tool to greet Ada.

模型调用 `greet` 工具,收到结果:**`Hello, Ada!`**

把这段代码拆开,插件的骨架只有四块:

1. **`name`**:插件叫什么;

2. **`inject`**:声明"我需要依赖什么服务"——这里声明需要 `tools`(工具注册表),Cordis 会等它就绪了才加载你;

3. **`apply(ctx)`**:插件加载的入口,所有注册动作都发生在里面;

4. **`ctx.tools.register(defineTool({...}))`**:注册一个模型可以调用的工具——`parameters` 告诉模型该传什么参数,`execute` 真正执行,`output.render` 决定结果怎么展示。

**这一层你只需要记住**:写插件 = 声明依赖(inject)+ 注册点什么(apply 里)。不继承任何东西、不实现任何接口——就是导出三个字段加一个函数。

---

## 第四层:插件 = 可逆副作用

> 论文把这套机制称为 **revertible effects(可逆副作用)**——日常说就是:插件卸载时,它注册过的东西被自动、干净地撤销。

到这一层,才触及"一切皆插件"的真正内核。

前一层我们 `register` 了一个工具。注意这个词:**注册**。注册不是"写死",注册意味着"装上了,也能卸下来"。dsh 的架构文档里有一句非常直接的话:

> **不存在需要打补丁的特权内核:扩展 dsh 的方式是把插件挂载到其他插件旁边,而各项注册都是副作用,会在其插件卸载时撤销。**

在 Cordis 里,每次注册都会返回一个 **disposer(释放函数)**。插件被卸载时,运行时自动逆序调用所有 disposer——你注册的事件监听器、挂上的工具、打开的服务,全部自动撤干净。你不用手动写"清理"逻辑,只要别把注册动作写成"一次性"的。

**真实例子:官方 tool-bash 插件怎么声明**

这是 `packages/shell/tool-bash/src/index.ts` 的真实开头:

```ts

export const name = 'tool-bash'

export const inject = ['tools', 'shell', 'systemPrompt', 'shellEnv']

```

一行 `inject`,声明了四个依赖服务。Cordis 会根据这些声明**自动推导加载顺序**,不用你手动排启动序列;插件卸载时,这四项依赖的注册项按逆序撤销。`bash`、`PTY`、LSP、文件系统这些工具,全是这样平级注册出来的——所以文档才敢说:

> 文件系统与进程提供方共享同一个执行世界,因此把它们指向远程沙箱,也就把 Bash、PTY 和 LSP 一并搬了过去,无需提供方专用 fork。

**第二个真实例子:三行配置接一个外部记忆系统**

官方 `examples/mcp-memory/engram.cordis.yml`,把第三方记忆服务 Engram 接进 dsh,整个文件就这么几行:

```yaml

- insert:

- id: memory-engram

name: '@deepseek-ai/dsh-mcp-client'

config:

serverName: engram

transport: stdio

command: engram

args: [mcp]

cwd: !!js process.cwd()

```

`mcp-client` 这个插件负责把 MCP 协议翻译成 dsh 的工具——外部世界的日历、邮件、记忆库,接进来都是一段配置,不需要给每个外部系统写专用代码。

**这一层你只需要记住**:插件不是"装了就不能拆",而是"拆的时候自动把自己弄干净"。可逆性,是插件能安全动态装卸的前提。

---

## 第五层:插件 = 让 AI 自己给自己装插件

前四层都是人操作插件。最后一层,角色互换:**AI 自己给自己装插件**。

dsh 出厂自带四档 agent preset,其中一档叫"创造模式"——它是一组**自指的 Cordis 工具**(`cordis_mount` 等)。选这个 preset 后,Agent 可以检查当前运行时的插件树,并**动态挂载或卸载临时插件**:模型临时写一个事件监听器、注册一个新工具、提供一个服务,任务完成后再卸掉。

**真实例子:给自己造一个官方没有的 UI**

官方演示里,一个接入了 V4 Pro 的 Harness,**给自己创建了官方 Web UI 没有的"三栏模式"**——不是调配置,是真的在运行时自己写了一小段插件代码挂上去。

这听上去像"让汽车在高速公路上给自己换发动机",所以它没有默认打开,信任等级被标注为**等同于 shell 访问权限**。同时有严格的边界:

> 临时插件只存在于进程内存里,不写文件、不装包、不改配置,重启即消失。

而它之所以敢这么干,恰恰是因为第四层的"可逆副作用":临时插件挂载和卸载都走标准的插件生命周期,注册项有确定的清理路径——**自修改式 Agent 不会留下一堆无人认领的残留**。写代码的人只声明"我要做什么",框架保证"卸载时怎么撤销"。

**这一层你只需要记住**:当插件的装卸足够安全、足够干净,AI 就能在运行时改装自己,还不用担心改坏。

---

## 五条隐性方法论

爬完五层台阶,真正值钱的不是"插件怎么装",而是这几条可迁移的判断:

**1. 没有内核,就没有补丁竞赛。** 一切皆插件的本质是:系统里不存在"只有官方能改"的特权区。任何能力都能被旁边挂一个插件覆盖——包括循环逻辑、安全策略、UI 本身。这消灭了"等官方发版本"的等待成本。

**2. 注册即副作用,卸载必可逆。** 从"开发者自觉记得清理"到"运行时保证清理",是插件系统从玩具走向可信的分水岭。判断一个"插件化"方案成不成熟,先问一句:**它卸下来的时候,能干净吗?**

**3. 配置即架构。** 一个 `cordis.yml` 就是插件行列表,patch 按 id 整体替换某一行——改配置等于改架构,且不用碰源码。`dsh --profile web --dump-config` 打印出的任何条目,都可以由你自己的 patch 替换。

**4. 依赖靠声明,不靠排队。** `inject` 把"谁先加载"从手工编排变成自动推导;服务消失、变化时框架主动通知,而不是你轮询。复杂系统最贵的部分——启动顺序——被一行声明消灭了。

**5. 从 App 到副作用,是同一个东西的五个视图。** 用户看到的是 App,配置看到的是开关,开发者看到的是代码,架构师看到的是可逆副作用,Agent 看到的是"自己能改的零件"。这五个视图没有谁更正确,只是站在不同的台阶上。

---

## 怎么抄作业

**给个人开发者**:先别写代码。去把 dsh 跑起来,装一两个社区插件(视觉、模型切换),感受"装 App"和"拧配置"这两层;然后照着官方教程写一个 `greet` 工具——十几行代码,你会突然明白"注册"两个字的分量。从用插件,到写插件,是理解 Agent 框架成本最低的入口。

**给 Agent 团队**:学习"依赖声明 + 可逆注册"的组织方式——每个能力单元都声明它需要什么、注册它提供什么、卸载时自动清理什么。这比任何微服务治理文档都更接近"组合"的本质。同时建立**信任等级**意识:一个 preset 的权限恰好等于它所引用插件的权限,创造模式与 shell 访问同级——Agent 能力越强,这句话越要刻在墙上。

**给 Leader**:看深一层——"插件化"不是功能清单,而是卸载路径、依赖边界、供应链审计三件事。安装一个 Git 托管的 bundle,往往要授权它的 `prepare` 脚本——**在插件激活之前,这段代码就已经在宿主机上执行了**。所以别只看"生态繁荣",先问:装进来的东西,谁能审、谁能卸、出事了怎么回滚?

---

模型的参数会过时,方法论会留下。

DeepSeek Harness 的"一切皆插件",表面是一句口号,内核是五个字:**组合优于特权**。而组合这件事,最难的不是"拼起来",是"拆得干净"。

从 App 到可逆副作用,从装插件到自己写插件——五层台阶走完,你会发现"插件"根本不是一种功能,而是这个行业关于"复杂系统怎么造"的最新答案。