让chatgpt来给我讲解下deepseek harness的插件体系

最近deepseek发布了harness,试用了一下结合deepseek-4-flash,用它尝试写一个php的现代版phpmyadmin,因为也是一个比较重要且有一点复杂度的应用
试了下除了数据展示有点问题,修了一轮就完成了,整体表现还是不错的
然后看了下它主打的是万物都是插件
这个概念还是比较复杂的,尝试让gpt老师帮我讲解下
DeepSeek Harness 就像一个“由很多小员工临时组成的公司”。
没有一个超级总经理包办所有事情:

  • 一个员工负责问大模型;
  • 一个员工负责读文件;
  • 一个员工负责运行命令;
  • 一个员工负责审批危险操作;
  • 一个员工负责保存聊天记录;
  • 一个员工负责显示网页界面。
    这些“员工”就是插件。
    需要什么员工,就把什么员工叫来;不需要了,就让它离开,并自动收拾自己留下的东西。

用一个真实请求走一遍

你对 DeepSeek Harness 说:

帮我读取 package.json,看看项目用了哪些依赖。
系统内部大致发生了这些事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
你输入问题

Agent Loop 插件:决定下一步做什么

LLM 插件:把问题发给 DeepSeek

DeepSeek 返回:我要调用 read_file

权限插件:这个文件允许读吗?

文件工具插件:读取 package.json

日志插件:记录本次工具调用

Agent Loop 插件:把文件内容再次交给 DeepSeek

DeepSeek 生成最终回答

Web UI 插件:把回答显示出来

关键点是:
上面每一个步骤都可以由不同插件负责。
比如你可以:

  • 把 DeepSeek 换成其他模型;
  • 把本地文件系统换成远程沙箱;
  • 给文件读取前增加权限检查;
  • 给模型调用增加重试;
  • 给工具调用增加日志;
  • 换掉整个 Agent Loop。
    其他部分不需要跟着重写。

为什么普通插件系统做不到这么灵活

普通软件的结构经常是:

1
2
3
4
固定核心程序
├── 插件位置 A
├── 插件位置 B
└── 插件位置 C

核心程序是老板,插件只能填几个预留位置。
DeepSeek Harness 更像:

1
2
3
4
5
6
7
极小的组装平台
├── Agent Loop 插件
├── 模型插件
├── 工具插件
├── 权限插件
├── 存储插件
└── UI 插件

连“核心 Agent 怎么循环运行”本身也是插件。
所以它不是:

一个 Agent,加上一些插件。
而是:
很多插件组合起来以后,才形成一个 Agent。

插件之间怎么找到对方

假设你写了一个天气工具插件。
它不能一启动就直接工作,因为它要先找到“工具管理器”。
所以它会声明:

1
我需要:tools 服务

代码里大概是:

1
export const inject = ['tools']

意思就是:

只有工具管理器存在时,才启动我。
系统启动时可能出现这种情况:

1
2
3
4
5
6
天气插件:我需要 tools
工具管理器:还没启动
天气插件:那我先等着

工具管理器启动
天气插件:条件满足,现在开始工作

如果工具管理器后来被替换:

1
2
3
4
5
6
7
旧工具管理器卸载

天气插件也暂时卸载

新工具管理器启动

天气插件重新启动,并连接新管理器

这就是 DeepSeek Harness 很特别的地方:

插件依赖不是只在启动时检查一次,而是一直被系统关注。

ctx 到底是什么

插件启动时会拿到一个 ctx

1
2
3
export function apply(ctx) {
// 使用 ctx 做事
}

你可以把 ctx 理解成“公司总机”。
插件不知道其他插件住在哪里,只需要问总机:

1
2
3
4
5
ctx.tools       // 工具管理器
ctx.llm // 模型管理器
ctx.sessions // 会话管理器
ctx.fs // 文件系统
ctx.sandbox // 沙箱

比如天气插件说:

1
ctx.tools.register(weatherTool)

意思就是:

总机,请把我的天气能力登记到工具管理器里。
这样天气插件不需要自己找到 Agent Loop,也不需要直接修改模型提示词。工具管理器会负责把天气工具告诉模型。

插件卸载时,怎么避免留下垃圾

这是整个系统最值得理解的设计。
假设天气插件启动后做了三件事:

1
2
3
1. 注册 weather 工具
2. 监听工具调用事件
3. 启动一个定时器

如果只是粗暴地删除插件代码,可能留下:

  • 一个无法调用的 weather 工具;
  • 一个永远存在的事件监听器;
  • 一个一直运行的定时器。
    DeepSeek Harness 要求插件在创建东西时,同时登记“怎么撤销”。
    可以理解成:
    1
    2
    3
    4
    5
    6
    7
    8
    创建 weather 工具
    同时记下:卸载时删除 weather 工具

    添加事件监听器
    同时记下:卸载时移除监听器

    启动定时器
    同时记下:卸载时停止定时器
    于是卸载插件时,系统会自动倒着清理:
    1
    2
    3
    停止定时器
    移除监听器
    删除 weather 工具
    所以热更新才比较可靠。
    修改天气插件代码后,系统可以:
    1
    2
    3
    4
    5
    完整清理旧天气插件

    重新加载新代码

    重新注册新天气工具
    而不是让新旧两套东西同时残留。
    这个“创建时顺便记录撤销方法”的机制,技术上叫 effect

Fiber 又是什么

现在再引入 Fiber 就很容易了。
Fiber 可以理解成:

某个插件这一次运行的“工作档案”。
里面记录着:

  • 这个插件是谁;
  • 它在等哪些服务;
  • 它现在有没有启动;
  • 它注册过什么东西;
  • 卸载时要清理什么;
  • 它是否启动失败。
    一个插件大致会经历
    1
    2
    3
    4
    5
    6
    7
    8
    9
    等待条件

    正在启动

    正常运行

    正在清理

    已经卸载
    对应的技术名称是:
    1
    2
    3
    4
    5
    PENDING
    → LOADING
    → ACTIVE
    → UNLOADING
    → DISPOSED
    所以 Fiber 不是线程,也不是子进程。
    它只是插件的一份“生命周期档案”。

插件怎么插手系统流程

有些插件提供能力,比如文件系统。
另一些插件不提供新能力,只想在某个流程中插一脚。
例如权限插件想在执行 Bash 前检查命令:

1
2
3
4
5
6
模型请求执行 rm ...

权限插件检查

允许:继续执行
拒绝:直接返回错误

这个过程很像高速公路收费站:

1
2
3
4
5
6
请求
→ 审批站
→ 权限站
→ 超时站
→ 日志站
→ 真正执行

每一站都可以:

  • 检查请求;
  • 修改请求;
  • 记录请求;
  • 放行;
  • 拒绝继续。
    在代码里,放行通常是调用:
    1
    next()
    不调用 next(),就表示到此为止。
    这就是它所谓的 waterfall。本质上就是一条可插拔的中间件链。

Bundle 和 Profile 又是什么

这里最容易被名词绕晕。

Plugin 是员工

真正执行工作的代码。

Bundle 是部门安装包

一个 Bundle 可能一次带来很多插件,例如:

1
2
3
4
5
6
Web Bundle
├── Web 服务器插件
├── 浏览器通信插件
├── 聊天界面插件
├── 设置页面插件
└── Web 启动插件

Profile 是公司组织方案
比如:

1
2
3
web Profile
├── 基础 Bundle
└── Web Bundle

而:

1
2
3
headless Profile
├── 基础 Bundle
└── 命令行 Bundle

因此:

1
dsh --profile web

可以理解成:

按照 Web 这套组织方案,把对应部门和员工全部组装起来。

安装一个插件时发生什么

执行:

1
dsh plugin --profile web add some-plugin

大致等于:

1
2
3
4
5
1. 用 pnpm 下载 npm 包
2. 查看这个包有没有声明自己是 DSH Bundle
3. 找到它携带的插件配置
4. 把它加入 web Profile
5. 下次启动时加载其中的插件

大概是这么个逻辑