DeepSeek Harness 上手实操:一切皆插件
2026 年 8 月 13 日,DeepSeek 开源了它的第一个 Agent 框架 dsh(DeepSeek Harness)。
项目出来之后热度涨得很快:上线 8 天 Star 破 10 万,现在已经超过 20 万。很多人第一反应也很自然:DeepSeek 这是也要做一个 Claude Code?
我一开始也是这么理解的。
但把项目装下来、翻完架构,再自己跑了一遍插件之后,我反而觉得,“DeepSeek 版 Claude Code”这个说法把它说小了。
dsh 确实可以拿来做编码 Agent,但它真正想解决的不是“怎么再做一个编码助手”,而是更底下一层的问题:
一个 Agent 到底应该由什么组成?这些东西能不能拆开、替换,再重新组合?
在 dsh 里,模型不是中心。工具、任务规划、调度、沙箱、存储、Agent Loop,甚至模型本身,都只是 Harness 里的一个部件。
这也是为什么它最核心的那句 slogan 不是“更强的 Coding Agent”,而是:
Everything is a plugin。
这篇就顺着这句话往下拆:dsh 到底是什么、它和 Claude Code 是什么关系、“一切皆插件”具体落到了哪、自己写一个插件是什么体验,以及这么高的热度下面,现在到底适不适合真拿来干活。
我的结论先放前面:
值得装,值得研究,尤其值得读它的架构;但目前还不建议直接接生产。
01它是什么:Agent = Model + Harness
先把定位说清楚。
DeepSeek Harness 不是模型,也不只是又一个 Claude Code,它首先是一个 Agent 框架。
它把 Agent 写成一个很简单的公式:
Agent = Model + Harness
Model 就是我们熟悉的 Claude、DeepSeek、GPT、Qwen 这些模型。
Harness 则是模型之外那一大堆真正让 Agent“能干活”的东西:工具、任务规划、调度、沙箱、存储、循环、会话,以及各种执行能力。
很多现有编码工具,其实已经把这两层揉得非常紧。比如 Claude Code,本质上就是 Anthropic 把自己的模型、工具链和 Agent 交互一起做成了一个完整产品。
dsh 走的是另一条路。
它把 Model 从中心位置拿下来,变成一个可以替换的组件。模型可以换,工具可以换,Session 可以换,Sandbox 可以换,理论上连 Agent Loop 都可以换。
这也是它和 Claude Code 最根本的区别。
有人做过实测(阿里云开发者社区的一整夜实测):dsh 自带的 provider 可以直接把 Claude Code 和 Codex 当子代理收进来——它会解析安装在 PATH 里的二进制,让它们去执行具体子任务。
这个关系其实很有意思。
因为它说明一种合法配置完全可以是:
外面跑 dsh,里面让 Claude Code 或 Codex 干某一段任务。
所以“我要不要从 Claude Code 换到 DeepSeek Harness”这个问题,多少有点问偏了。
Claude Code 更像一个已经打磨好的成品工具;dsh 更像把 Agent 背后的 Harness 层单独拆出来,交给开发者自己组装。
甚至可以说,一个偏“产品”,一个偏“底层框架”。
这也解释了为什么 dsh 第一眼看起来没那么顺手,但继续往架构里看,会越来越有意思。
DSH Desktop 支持多家模型服务商。
目前能接入 DeepSeek、OpenAI、Anthropic、Google Gemini,以及 xAI、Kimi、MiniMax、智谱 GLM 等主流服务。
手里有多个模型账户时,这个设置比较方便。写代码用一种模型,处理长文档换另一种,费用或速度有变化,也能随时调整。
02一切皆插件:这句话在 npm 层是 116 个包
“Everything is a plugin” 这种话我一般不会第一时间全信。
插件化大家都会说,最后到底是“支持几个扩展点”,还是“系统本身就是由插件拼起来的”,差别其实很大。
最简单的办法就是装一遍。
我在本机安装 @deepseek-ai/dsh,装完之后 node_modules 大约 288MB,里面躺着 116 个独立的 @deepseek-ai/dsh-* 包。
一个社区作者读源码后给出过另一组更细的数据:359MB、555 个 package manifest、255 个顶层依赖目录,其中 196 个带 @deepseek-ai 作用域。
他有句话形容得很准:
这不像“一个带插件的框架”,更像“一个恰好能启动的小型包仓库”。
比如:
dsh-llm
dsh-session
dsh-tools
dsh-sandbox
dsh-subagent
dsh-agent-loop
...
几乎每项能力都单独拆成了包。
再跑:
dsh --profile web --dump-config
它会把当前环境真正装配起来的插件树整个打印出来。
我本机跑出来是 503 行,前面一部分大概这样:
- id: llm
name: '@deepseek-ai/dsh-llm'
- id: session
name: '@deepseek-ai/dsh-session'
- id: agent
name: '@deepseek-ai/dsh-agent'
- id: agent-default-model
name: '@deepseek-ai/dsh-agent-default-model'
config:
provider: deepseek-official
model: deepseek-v4-flash
模型适配器、会话存储、Agent、默认模型,都是一排 id + 包名。
到这里,“一切皆插件”就不是一句宣传话了。
它是真的在把整个系统尽可能拆成插件。
当然还内置了大量的插件,我们可以尽情的筛选安装,好用的插件的确能提高效率。
这也是所谓“无特权内核”最直观的一层意思:
没有哪个模块天然就是不能碰的核心。
你想扩展 dsh,通常不是先 fork 核心,然后在里面打 patch;而是往现有插件树里再挂一个插件。
听起来只是开发方式不同,但对 Agent 这种还在快速变化的系统来说,差别其实很大。
因为今天你想换模型,明天可能想换 Sandbox,过两周又想换掉某一段 Agent Loop。如果每次都要去改一个巨大的核心仓库,最后很容易变成“能跑,但没人敢再动”。
03真正撑住插件体系的,是 Cordis
当然,光把代码拆成一百多个 npm 包,不代表架构就一定好。
如果这些包背后还是大量全局状态、事件监听和互相引用,那只会从“一个大项目难维护”变成“一百多个小项目一起难维护”。
dsh 这套插件体系真正关键的东西,在它底下的元框架 Cordis。
Cordis 对应一篇论文:《A Programming Paradigm for Spatiotemporal Composability》。
名字有点学术,但核心可以压成两个方向:
时间上的可逆,空间上的依赖响应。
先看时间。
Cordis 有一个很关键的概念叫 revertible effects,可逆效应。
简单理解就是:
插件往系统里加了什么,就应该知道以后怎么完整撤回来。
比如一个插件注册工具、监听事件、创建计时器。
传统做法通常是先:
register()
addListener()
setInterval()
卸载时再手动:
unregister()
removeListener()
clearInterval()
问题是,只要有一个地方忘了清理,就会留下脏状态。
Cordis 的思路是让运行时去追踪这些 effect。插件被卸载时,相关 effect 一起 unwind。
所以“装”和“卸”不再是两个互不相关的动作。
再看空间。
Cordis 里另一个概念叫 reactive coeffects。
名字有点绕,但落到 dsh 插件代码里其实很直接:
export const inject = ['tools']
这句话就是在说:
我这个插件依赖 tools。
等 tools 准备好以后,再加载我。
也就是说,插件不是假设“整个世界早就准备好了”,而是明确声明自己依赖哪些能力。
这两件事合起来之后,dsh 才敢把系统拆得这么散。
对工程师来说,最实际的好处就是一句话:
换东西,不一定要 fork 核心。
换模型适配器、换文件系统 provider、换执行环境,甚至换 Agent Loop,都可以围着插件和依赖关系来做。
这才是 dsh 比“支持几个 Plugin API”更值得看的地方。
04seam:换一个执行环境,不用把所有工具重写一遍
Cordis 之外,还有一个我觉得很实用的机制:seam(能力缝)。
它把能力拆成三个角色:
Service Definition:声明接口 Service Provider:实现接口 Consumer:使用接口
这套设计本身并不陌生。
真正有意思的是,dsh 里文件系统和子进程 provider 可以共享同一个执行世界。
举个比较具体的例子:
如果你把 Sandbox provider 指到一个远程环境,那么 Bash、PTY、LSP 这些能力可以跟着一起迁过去。
不用为了“远程执行”再分别维护:
Remote Bash
Remote PTY
Remote LSP
这一整套平行实现。
这类设计平时看文档不一定有感觉,但做过复杂 Agent 之后会很容易意识到它的价值。
Agent 一旦从“本地帮我改两行代码”走向远程 Sandbox、容器、集群,执行环境怎么统一搬走会变成一个很实际的问题。
05自己写一个插件,其实没有想象中复杂
只看架构很容易越看越抽象。
所以我实际跑了一下它的工具插件。
一个最小插件大概长这样:
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'my-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'count_words',
description: 'Count words, lines and characters in a string.',
parameters: {
text: { type: 'string', required: true },
unit: { type: 'string', enum: ['word', 'line', 'char'] },
},
output: {
schema: { type: 'object', properties: { count: { type: 'number' } } },
render: (_args, value) => [{ type: 'text', text: `${value.count}` }],
},
async execute(args, exec) {
const unit = args.unit ?? 'word'
const count = unit === 'line'
? args.text.split(/\r?\n/).length
: args.text.trim().split(/\s+/).filter(Boolean).length
return { count, unit }
},
}))
}
代码不长,但里面几处设计很能说明 dsh 的思路。
第一,parameters 不是一份单纯写给模型看的 JSON 描述。
它同时干三件事:
推断 TypeScript 类型、在
execute之前校验模型生成的参数、让模型知道这个工具应该怎么调用。
所以到了:
async execute(args, exec)
这里时,args 已经不是模型随便吐出来的一坨 JSON。
它前面有 schema 兜着。
第二:
ctx.tools.register(...)
不是简单把工具塞进一个全局 registry。
这个注册本身就是前面说的 effect。
插件卸载,工具注册也会跟着撤销。
第三,PTC mode(原 Code Mode)里可以直接调:
await tools.count_words({...})
参数和返回值类型仍然来自同一份 schema。
而且调用走的还是正常执行管线,包括权限策略,并不是“为了方便代码调用,再偷偷开一条旁路”。
这个设计我比较喜欢。
因为很多框架一开始说“工具统一”,最后模型调用一套、代码调用一套、测试又一套,时间一长就会慢慢长成三个系统。
dsh 至少在这里是想把它们压回同一个执行入口。
06真正把插件挂进去,只需要一个 patch
有了 apply(ctx) 还不够。
dsh 还得知道插件文件放在哪、什么时候加载。
官方教程的最小闭环其实不复杂,就三步。
第一步,保存插件文件。
比如:
scratch-plugin/src/my-tool.ts
里面就是刚才那段代码。
这句很关键:
export const inject = ['tools']
它告诉 Cordis:
等 tools 服务准备好之后,再加载这个插件。
所以进入 apply() 时,正常情况下 ctx.tools 已经可用,不需要自己到处判空。
第二步,写一个挂载点 cordis.yml。
- insert:
- id: my-tool
name: '/绝对路径/scratch-plugin/src/my-tool.ts'
本质上就是告诉 dsh:
把这个插件插进当前插件树。
第三步,用 --patch 启动。
npm 安装方式:
dsh web --patch ./scratch-plugin/cordis.yml
如果从源码跑:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
起来之后,再执行:
dsh --profile web --dump-config
插件树末尾就能看到:
id: my-tool
到这里才算真的把插件挂进去。
接下来有两种用法。
一种是正常 Agent 工具调用。
工具描述进入模型可见的上下文,模型自己判断什么时候调用。官方教程里的例子是让模型用 greet 工具问候 Ada,然后拿到:
Hello, Ada!
这条路需要一个真的能用的模型 Key。
我本机没有 DeepSeek Key,所以没有继续跑模型对话。
另一种是 PTC mode。
工具可以直接变成:
await tools.count_words({ text, unit })
参数和返回值类型还是从同一个 schema 推出来,调用也继续走 dsh 正常的权限和执行管线。
更有意思的是卸载。
去掉 --patch 重启,或者从 cordis.yml 里删掉那一行,工具注册会跟插件一起 unwind。
不用再单独写一堆:
removeListener()
unregister()
clearTimer()
跑到这里,“可逆效应”才从一个论文术语变成真正能感受到的工程机制。
07我最认可的,反而是它的会话日志
插件系统看起来最抢眼,但继续往下看之后,我反而更喜欢 dsh 的另一条约束:
model-visible means logged。
翻成大白话就是:
只要模型看见过,就必须能从日志里重建出来。
dsh 的会话日志是 append-only。
模型请求里的 prompt 片段、每一次工具调用的输入输出、原始响应,都会进入日志。
Fork、回放、转录、Telemetry、持久化,也都围着这条流继续派生。
它还有一个 Trajectory 视图,可以继续往下看:
当时到底激活了哪段 system prompt Agent 调了什么工具 工具输入是什么 最后返回了什么
这条约束看起来没“一切皆插件”那么新鲜,但真正做过复杂 Agent 后,很容易知道它有多重要。
一个社区作者在前述 Medium 文章里提到,他曾经花两周排查一个生产 Agent 为什么干了一件蠢事,结果手上只有残缺的 Trace。
所以他看到 dsh 这条约束后,直接评价它是:
“这个项目里最好的想法。”
这个评价我基本认同。
很多 Agent 系统都是先把功能跑起来,等出问题以后再补日志、补 tracing、补 dashboard。
但真正难排查的,从来不是“这个函数有没有报错”,而是:
模型当时到底看见了什么,它为什么会做这个决定。
dsh 相当于把这个问题往前推了一层。
不是出了问题再想办法猜,而是一开始就规定:
模型能看到的东西,必须被记录。
这才是可观测性真正应该在的位置。
08四种模式,我更关注 Headless
dsh 目前预设了四种运行模式:
Standard:完整编码 Agent,带文件编辑、Shell、Skill、子代理等能力。 PTC:模型先出脚本再执行,更适合批量工具调用。 Minimal:只保留 Bash + 文件编辑,主要用于 benchmark。 Creative:自定义 Agent preset,用 Agent 造 Agent。
但这几个模式里,我更关注 Headless。
Claude Code / Codex 平时最常见的用法,还是人在终端里和 Agent 一来一回地交互。
这种体验很好,但不是所有任务都需要人一直盯着。
比如:
把某个 fetch() 迁移扩散到 src/api 下几十个文件。
或者逐模块扫描、逐模块修改、逐模块验证。
这类任务更适合程序直接驱动 Agent,而不是每处理一个文件都在终端里聊一轮。
阿里云的实测提到,官方提供了 Python SDK,可以通过 DeepSeekHarness 上下文管理器,再对每个模块调用一次:
harness.run()
来做逐文件、逐模块的无人值守处理。
这里需要说明一下:
dsh 本体仍然是 TypeScript / Node 技术栈。
Python SDK 只是它提供出来的批处理入口,不是说 dsh 本身变成了 Python 框架。
这个能力我觉得很实在。
因为如果 Agent 真要从“个人编码助手”往工程流水线里走,Headless 几乎迟早都会变成刚需。
09成本可能有优势
有博主实测还引用过一组第三方 benchmark。
30 个 Agent 任务、8 种 Harness 配置下:
Pi Agent 每成功一个任务大约 $0.028。 Claude Code 是 $0.195。
大约相差 7 倍。
Claude Code 的速度更快,中位时间约 122.7 秒。
这个数字当然很吸引眼球。
但我不太建议直接把它写成“dsh 比 Claude Code 便宜 7 倍”。
因为这毕竟是第三方实测,模型、任务、Harness 配置都会直接影响最后结果。
更值得看的其实不是“7 倍”本身,而是:
当 Model 和 Harness 被拆开后,你终于有机会针对不同任务重新组合成本、速度和能力。
交互编码可以继续用更强的模型。
大量批处理可以换另一套模型和 Harness 配置。
真正有价值的是这个自由度。
10真上手之后,坑也不少
说完优点,再说现在上手会真实撞到的问题。
1. 没有终端交互式 TUI。
dsh 当然有 CLI,前面的 --dump-config 就是在终端跑的。
但真正交互式使用主要还是 Web,Headless 又是另一条路线。
如果你已经习惯 Claude Code 那种一直待在终端里的工作方式,切过来会比较明显地不适应。
2. v0.1 还很早。
官方已经明确说了,未来会出现破坏兼容性的变更。
项目早期版本迭代也很快,社区实测里还有人遇到过会话偶发冻结、需要刷新页面的问题。
现在这个阶段,连 API 本身都还可能继续动。
3. 学习曲线确实不低。
想真正看懂它,至少得继续理解:
Cordis、Plugin、Profile、Bundle、Provider、Seam。
如果你的目标只是“装完马上让 Agent 帮我改代码”,Claude Code 这种成品工具明显更省事。
4. 模型要自己准备。
dsh 本身不提供模型。
DeepSeek、Claude 或其他 Provider 的 Key 都得自己配。
5. 安装比较重。
前面说了,我这里是 288MB、116+ 个 dsh 相关包。
插件拆得很细,代价就是依赖也多。
在受限环境里,安装体验并不一定舒服。我自己就在沙箱里撞到过 ~/.dsh 的 EPERM。
6. 本地 Runtime 目前还不算一等入口。
社区实测里提到,provider 目录已经收了大量云端供应商,但 Ollama / LM Studio / llama.cpp 这类常见本地 Runtime,并没有特别顺手的开箱入口。
不是完全不能跑。
可以走自定义 provider 或 OpenAI-compatible 配置,社区也已经有人用 DGX Spark + Ollama 挂 Qwen 27B 接进 dsh。
但现在还不是这种体验:
安装 -> 选择 Ollama -> 直接跑
如果你非常依赖本地模型,这一点目前还是得自己折腾。
11写在最后
所以看完这一圈,我对 dsh 的判断其实挺简单。
方向我很认可。
但现在不会拿它替掉手上的 Claude Code,更不会急着接进生产。
我觉得它真正值得看的,不是“DeepSeek 终于也做了一个 Coding Agent”。
而是它把 Agent 底下那层长期藏在产品内部的 Harness,单独拿出来重新设计了一遍。
模型可以换。
工具可以换。
文件系统可以换。
执行环境可以换。
Agent Loop 甚至也可以换。
再加上可逆 effect、seam、append-only session log 这些东西,至少在架构层面,它确实把“一个 Agent 框架应该怎么拆”这件事往前推了一步。
但另一方面,现在它仍然是很早期的版本。
开发者预览、破坏兼容变更、没有终端 TUI、本地 Runtime 支持不够顺手,生态也才刚开始长。
更重要的是,官方在 8 月 27 日的 release notes 里自己也提醒:
项目尚未完成安全审计,Sandbox、审批和权限控制不能被视为可靠的安全隔离。
这基本已经替我们回答了“能不能直接接生产”。
至少现在,我不会。
比较合适的用法反而是:
装下来,跑一遍插件,看看 Cordis,读读它的 architecture。
如果你自己正在做 Agent Framework、Coding Agent、长任务 Harness,里面不少设计都值得参考。
至于生产?
再等一等。
现在的 Claude Code 是一个已经可以拿来干活的产品。
DeepSeek Harness 更像一个刚把地基结构公开出来的新框架。
两者没必要二选一。
甚至以后真正有意思的场景,很可能就是:
Claude 干 Claude 擅长的事,DeepSeek 干 DeepSeek 擅长的事,外面再有一层 Harness 决定到底该叫谁来。
这可能才是 dsh 真正值得关注的地方。