作者: · 发布:

重磅!一文读懂 DeepSeek Harness 插件!

最新:DeepSeek Harness

前几天,我们给大家带来了很多教程:1. DeepSeek Harness保姆安装教程来了!2. DeepSeek Harness 插件教程来了!3. DeepSeek Harness桌面版和CLI来了!

今天我们讲点硬核的,带大家看看 DeepSeek Harness的核心架构 Cordis。

它的核心理念是一切皆插件。模型适配器、工具注册表、会话日志、agent loop 本身,每一部分都是插件,都可以在运行时替换。换一个模型提供方不用改源码,换一个沙箱后端不用重启,换 agent loop 本身也不用动框架。

这种”任意环节可替换”的设计,是 DSH 和传统 Agent 框架最大的区别。下面分三层拆开:先看整个项目怎么组装,再看插件系统怎么运转,最后看插件动态能力的三个关键机制。

一、DeepSeek Harness 是怎么组装出来的?

DSH 不是一个装好了就定型的应用。启动时按顺序叠加各层配置,最终在内存里形成一套完整的插件组合开始运行。

底层是 Cordis 微内核。 Cordis 是一个专门管插件的框架,只管一件事:插件什么时候加载、什么时候卸载、彼此怎么协作。它最初由一位叫 Shigma 的开发者为一个聊天机器人框架 Koishi 编写,2022 年独立开源。DeepSeek 把 Shigma 和 Cordis 一起雇了,把整套源码并进了自己的仓库。产品的每一部分都是 Cordis 插件,连驱动 Agent 运转的主循环本身也是。

中间是三层组装机制。 这三层解决的是”这套插件组合怎么搭起来”的问题。

  • Profile 是一份存在本地的组装方案,列出自己要叠放哪些组合包,也存用户自己写的配置补丁。
  • Bundle 是组合包,把一组插件和它们的配置打包分发。装一个组合包,就等于一次装好一组配套插件。
  • Patch 是补丁,按名字定位到某个具体插件,替换它的配置。

三层按顺序叠加:先装各个组合包,再打 profile 的补丁,再打本地的补丁,最后还能在启动命令里临时加一层补丁。每一层都能覆盖上一层的结果。

重磅!一文读懂 DeepSeek Harness 插件!

dsh-base 是每个 profile 必装的第一层组合包,装上它就有了模型适配器、工具、数据持久化、安全策略、遥测这些基础能力。后续的组合包和用户自己的补丁按名字覆盖它装好的插件。

顶层是四种运行模式。 Standard、Code、Minimal、Creator 是同一套插件的不同组装。Standard 功能最全,Code 专门面向编程,Minimal 只留 Shell 和文件编辑两个工具,Creator 开放给想做实验的人。切换模式就是换一套插件组合,不改任何代码。

这三层从底到顶:Cordis 管单个插件的加载和卸载,三层组装管整套组合怎么搭,四种模式管最终跑哪套组合。整个项目就是通过这套流程组装出来的一套插件组合。

二、一切皆插件:先看懂 Cordis 的核心词汇表

插件是 DSH 的基本单元。要理解插件怎么工作,先认识五个核心概念。不用记名字,理解它们各自解决什么问题就行。

重磅!一文读懂 DeepSeek Harness 插件!

插件本身有三种写法。 写一个简单的功能,用一个函数就行;写一个要被别人调用的能力,用一个类;两种都不想用,还可以用一个对象。三种写法地位平等,加载和卸载走同一条路。

// 函数写法:最简单,适合注册几个监听器
export const name = 'greeter'
export function apply(ctx: Context) { /* ... */ }
// 类的写法:适合提供给别人调用的能力
export class GreeterService extends Service {
  constructor(ctx: Context) { super(ctx, 'greeter') }
}

Context 是存放各种能力的地方。 每个能力有一个固定的名字,比如 ctx.tools 是工具、ctx.llm 是模型、ctx.sessions 是会话。别的插件通过名字来取用能力,不关心这个能力具体是谁写的、跑在本地还是远程。某个能力可以随时换成另一个实现,取用它的插件不用改。这是整个系统能”任意替换”的基础。

Service 是一种可以被别人取用的能力。 用类写一个 Service,它会自动注册进去;插件卸载时,它自动注销。

Fiber 是每个插件的生命周期记录。 一个插件从诞生到消失,经历六个阶段:排队等待依赖、正在启动、正常运行、正在卸载、完全消失,中间还可能启动失败。这条记录就是 Fiber。它是动态加载的核心。

inject 是插件的依赖声明。 插件开工前先声明自己需要哪些能力,框架会等这些能力都就位了才让它启动。启动顺序由依赖关系决定,不由写的先后顺序决定。

五个概念之外,插件之间不直接互相调用,全靠事件总线通信。一个插件发出通知,谁在监听谁响应,发通知的插件不用知道有谁在听。事件有五种分发方式:

重磅!一文读懂 DeepSeek Harness 插件!

最后一种 waterfall 是实现拦截的模式。每个监听者拿到一个”继续往下传”的开关,调用它就把工作交给下一个监听者,不调用就直接拦下。DSH 的工具执行流水线就是四段 waterfall 串起来的。

这五个概念加事件总线,构成了插件系统的基础。下面展开三个让”动态”变得可靠的关键机制。

Fiber、inject、effect:插件动态能力的三个关键机制

Fiber:让插件的加载与卸载都有迹可循

每个插件实例都有一条 Fiber 记录它的状态,在六个阶段之间转换:

排队等待 → 正在启动 → 正常运行 → 正在卸载 → 完全消失
                    ↘ 启动失败

流程是这样的。声明一个插件后,Cordis 检查它声明的依赖。依赖都满足了,就启动它,进入”正在启动”,跑完进”正常运行”。依赖没满足,它就一直在”排队等待”,不报错,静默等着。

卸载反过来走:正常运行 → 正在卸载(执行清理)→ 完全消失。

“排队等待”这个状态最容易被误判成”插件坏了”。一个插件声明需要某个能力,但那个能力没装上,它会一直排队,什么都不输出,进程也可能静默退出。想知道哪个插件在排队,遍历一遍所有插件的状态就行:

for (const runtime of ctx.registry.values()) {
  for (const fiber of runtime.fibers) {
    if (fiber.state === FiberState.PENDING) {
      console.log(`${fiber.name} 在排队等待,缺少某个依赖`)
    }
  }
}

热重载走的就是这条状态机。 开发时改了一个插件的代码保存,监视插件会发现文件变了,先把旧实例卸载到”完全消失”,再加载新代码走一遍”排队 → 启动 → 运行”。改配置文件 cordis.yml 本身也会触发更新,loader 按名字比对,只重新加载变化的部分。

inject:哪个插件先启动,依赖关系说了算

inject 不是启动时检查一次就完事,是持续跟踪的依赖关系。

加载顺序无关紧要。 插件 A 声明需要插件 B 提供的能力,Cordis 会让 A 排队,直到 B 就位。配置文件里 A 写在 B 前面还是后面,结果一样。把 B 彻底移除,A 就停在排队状态,不崩溃,也不会跑一半。决定插件何时启动的是依赖关系,不是文件里的先后顺序。

运行中仍在跟踪。 应用跑着的时候,如果某个能力消失了(提供它的插件被卸载或热替换),所有依赖它的插件也会跟着卸载;等这个能力恢复了,它们再重新加载。这能防止一个插件已经拿到某个能力的引用,那个能力却突然不见了。

这条机制的实际威力在一个场景里最明显。把默认的 Shell 执行插件换掉,挂上另一个提供方,所有用到 Shell 的插件都会自动重启、用上新的实现。不用改这些插件的一行代码,整条依赖链自动重排。

effect:插件卸载了,留下的副作用怎么办?

动态加载能跑不难,难的是卸载干净。插件启动时可能开了定时器、注册了监听器、挂载了子插件,卸载时这些都得清理干净。Cordis 的办法是 effect:插件通过框架 API 做的所有注册,都属于副作用,插件卸载时框架会自动撤销。

框架没直接管的资源(比如定时器、网络连接、文件监视),包进 ctx.effect() 里,同时返回一个清理函数:

export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(() => console.log('tick'), 200)
    return () => {
      clearInterval(timer)      // 卸载时跑这个
      console.log('heartbeat cleaned up')
    }
  })
}

上面这段里,apply 里跑的是加载逻辑(开定时器),返回的函数是卸载逻辑(关定时器)。插件进入”正在卸载”时,框架自动调用这个清理函数。

几种常见操作天生就受 effect 管理,不用自己写清理逻辑:注册事件监听器,插件卸载时监听器自动移除;挂载子插件,父插件卸载时子插件跟着清理;往工具注册表里注册一个工具,插件卸载时工具自动注销。

清理函数的执行有条纪律:按注册的反序启动,但多个异步清理会并发跑。如果清理步骤有先后顺序要求,放进同一个清理函数里依次执行。

这三个机制合起来,动态加载才从”能用”变成”可靠”。Fiber 管生命周期,inject 管依赖,effect 管清理,三条各管一段,互不重叠。

重磅!一文读懂 DeepSeek Harness 插件!

一个真实的例子:工具运行时如何串起整条链路

DSH 里有个叫工具运行时的核心组件,上面讲的五个概念和三个机制在它身上全用上了。

它本身是一个能力类,会自动注册到系统里。它往系统里注册了 6 种事件,其中 4 种是 waterfall 拦截型、2 种是广播型。它声明自己依赖”提示词组装”这个能力,等这个能力就位了才启动,启动时立刻注册自己要用的提示词片段。

它提供一个”注册工具”的方法,调用方每注册一个工具,拿回一个对应的卸载函数。想动态卸载某个工具时,直接调用这个函数即可。这就是”动态”在最细粒度上的样子。

工具的执行是一条四段流水线,每一段都是一个 waterfall 拦截点:

重磅!一文读懂 DeepSeek Harness 插件!

审批插件可以在”执行前”这一环直接拦下,工具的实际逻辑从没运行;结果屏蔽插件可以在”执行后”把成功结果改写成错误提示,模型看到的已经是改写后的内容。

同一个工具运行时还能服务不同的 Agent。每个 Agent 能看到哪几个工具,是沿着一层层继承关系算出来的:先继承全局工具,再叠加自己所在的组合包层的工具,最后应用访问限制过滤。一个 Agent 被限制了只能用某几个工具,不影响另一个 Agent。

一个真实的 harness 服务长这样:它是个能力类,注册多种事件,声明依赖,用 effect 管所有注册,用分层让不同 Agent 看到不同工具集。前面讲的所有概念和机制,在它身上都能找到对应。

写在最后

DSH 的插件架构是目前开源 Agent 框架里设计最彻底的一个。不是”支持插件”,是整个产品就是插件的组合,任意环节都可以在运行时替换。

这种设计给个性化实现 Agent 留了很多可能。不同场景需要不同的工具集、不同的上下文策略、不同的执行流程,在 DSH 里都是换一套插件组合的事。模型可以换,工具可以换,连 agent loop 本身都可以换。呈现方式也可以换,Web UI、CLI、API 是不同的前端插件,挂在同一棵插件树上。

后面这个方向会出现很多玩法。自定义 agent preset、第三方插件生态、不同场景的插件组合模板,都是值得关注的方向。Harness 现在还是 v0.1 开发者预览版,接口随时可能变,但架构方向已经很清楚。

单看”任意环节都能在运行时替换”这一条,我们就非常看好它的架构设计。

颜资源站长
颜资源站长 已发布 825 篇文章

资深互联网从业者,专注AI工具研究与实战应用。长期跟踪ChatGPT、Claude、Stable Diffusion等前沿AI技术,擅长将复杂的技术概念转化为通俗易懂的教程。运营颜资源小站,致力于为中文用户提供高质量的AI教程、开源项目推荐和数字资源整理。