DeepSeek Harness 实战:所谓“一切皆插件”,到底怎么拼?
最近 Agent 工具的“harness”层正在形成一个新战场。Prime Agent、Claude Code、Codex CLI 都在争夺这块地盘,DeepSeek 也抛出了自己的答案——DeepSeek Harness(dsh),一个 MIT 开源、基于 Cordis 插件内核的 Agent 运行框架。它在 2026 年 8 月 13 日发布开发者预览版后迅速登上 GitHub Trending。
但 dsh 不是 Claude Code 那种“装完就能用”的终端助手。它更像一个乐高底盘:模型、工具、技能、会话、沙箱、存储、循环、调度、UI,所有能力都是插件,通过配置层的堆叠与覆盖来组合。这意味着“会启动 Web UI”只是起点,真正会用它的标志,是理解它的四层配置合成模型。
这篇文章不会教你填 API Key。我会带你从 npx @deepseek-ai/dsh web 出发,拆解 profile、bundle、cordis.patch.yml、--patch 四层如何合成一棵真实的插件树,并给出 headless 模式与自定义插件扩展的实战步骤。
一、背景:为什么 agent 工具突然都在做“harness”?
过去一年,LLM 的普及路径经历了明显的分层:
- 模型层(DeepSeek、OpenAI、Anthropic)负责“思考”;
- IDE/客户端层(Cursor、Claude Code、Codex CLI)负责“把思考变成文件改动”;
- 而 dsh 这类 harness 层,则试图回答一个问题:如果把“模型 + 工具 + 状态 + 会话”全部拆成可替换组件,能不能让 Agent 既开放又可控?
dsh 的核心假设是:Agent = Model + Harness。模型负责推理,harness 负责让模型安全、持久、可审计地接触真实世界。它用 Cordis 作为插件元框架——这套机制在论文《A Programming Paradigm for Spatiotemporal Composability》里有形式化描述,核心思想是“副作用可逆”:插件可以动态挂载、卸载、替换,运行中也能修改自身能力。
但官方 README 也写得很直白:
DeepSeek Harness is currently in developer preview and is iterating rapidly. THERE WILL BE COMPATIBILITY-BREAKING CHANGES.
所以本文不会把它捧成 Claude Code 的替代者,而是把它当成一个需要理解架构才能用好的开发者预览品来实战拆解。
二、环境准备:真实版本号与安装命令
所有命令在 Windows、macOS、Linux 上通用。请注意 dsh 对 Node 版本有硬门槛,不要拿旧 LTS 硬跑。
2.1 前置条件
| 依赖 | 版本要求 | 来源 |
|---|---|---|
| Node.js | 22.19+ 或 24+(CI 覆盖 22.19 / 24 / 26) | 官方 docs/development.md |
| pnpm | 仓库锁定 pnpm@11.7.0,需 Corepack 启用 | 官方 docs/development.md |
| Git | 2.26 或更新 | 官方 docs/development.md |
| API Key | 可选;Web / headless / ACP 真实 API 测试需要 | 官方 CLI reference |
# 检查 Node 是否满足要求
node --version
# 输出应 >= v22.19.0 或 >= v24.0.0
启用 Corepack 并检查 pnpm
corepack enable
pnpm --version
输出应为 11.7.0 附近
2.2 两种启动方式
方式 A:npx 最快启动(适合尝鲜)
npx @deepseek-ai/dsh web
这条命令会下载并启动 Web UI,默认监听 http://127.0.0.1:3080。注意:dsh 目前故意不支持 --host 0.0.0.0,带这个参数会报 usage error。
方式 B:从源码构建(适合写插件、看架构)
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
从源码运行时,pnpm dsh <args...> 会走 TypeScript 入口,需要先做 pnpm run build。
三、dsh 的插件分层:没有“特权内核”
dsh 最吸引人的宣传点是 “Everything is a plugin”。但这句话不能只看字面。它的真实含义是:dsh 把 Agent 的每一层都拆成 Cordis 插件,并且没有任何一层在代码层面享有不可替换的特权。
下图展示了从内核到外部插件的分层结构:

图1:dsh 插件分层架构(概念示意图,非运行截图)。最底层 Cordis 只负责插件的加载/卸载与依赖关系;dsh-base 提供模型适配、工具、沙箱等基础能力;其上再选择 web-app 或 headless 应用形态;最外层是社区插件。
各层职责如下:
- Cordis 内核:插件元框架,负责服务注册、类型化事件、生命周期管理。它不实现任何 Agent 能力。
- @deepseek-ai/dsh-base:官方基础 bundle,包含模型适配器、工具集、持久化、沙箱/审批策略、settings、credentials、telemetry(默认关闭)。
- 应用层 bundle:
@deepseek-ai/dsh-web-app提供浏览器 UI;@deepseek-ai/dsh-headless提供一次性无 UI runner。 - out-of-tree 插件:通过
dsh plugin add安装的社区或私有插件,仓库通常带dsh-pluginGitHub topic 以便检索。
也就是说,“模型”不是 dsh 写死的,“UI”也不是 dsh 写死的。你想要换模型后端、换审批策略、甚至换一个完全不同的 UI 框架,理论上都只需改配置文件,不需要 fork dsh 源码。
四、真正会用它:四层配置合成模型
这是本文的核心。理解下面这张图,比记住任何安装命令都重要。

图2:dsh 配置合成顺序(概念示意图,非运行截图)。空配置树依次叠加 bundle patch → profile patch → home patch → --patch 覆盖层;后一层按“行”覆盖前一层,且 home 层优先级高于 profile 层。
4.1 profile 目录里有什么?
每个 profile 是一个独立的 Node 工作区,位于 $DSH_HOME/profiles/<name>/:
$DSH_HOME/profiles/web/
├── package.json # out-of-tree 插件依赖 + dsh.profile manifest
├── pnpm-lock.yaml # 插件锁定
├── pnpm-workspace.yaml # pnpm workspace 配置
├── cordis.patch.yml # 你自己的 patch 层
└── node_modules/ # 插件 bundle 实际落在这里
其中 package.json 里有一个关键字段:
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app"
]
}
}
}
bundles 的顺序就是配置合成的顺序。顺序变了,最终生效的配置可能完全不同。
4.2 四层合成顺序
官方 CLI reference 白纸黑字定义了合成顺序:
空根配置树
→ ① 各 bundle 的 patch(按 bundles 顺序)
→ ② profile 的 cordis.patch.yml
→ ③ home 的 cordis.patch.yml
→ ④ --patch 覆盖层(按 argv 顺序)
关键规则:
- 按行覆盖,不是深度合并。后面的层如果命中了某一行,就把该行的
config整体替换;没命中则保留前一层。 - 可以插入新行。patch 不只能覆盖,还能新增配置行。
- home 层优先级高于 profile 层。这一点反直觉。官方原话是:home-level patch “outranks the per-profile layer”,因为它被设计为“机器级偏好”,跨 profile 共享。
4.3 最容易踩的坑:把 !!js 表达式写死了
很多 bundle 的配置行会写类似这样的内容:
- id: webStartup
config:
port: !!js ctx.webStartup.port ?? 3080
这行的意思是:运行时去 ctx.webStartup.port 里取值,取不到就用 3080。于是命令行 --port 8080 才能生效。
但如果你在自己的 cordis.patch.yml 里直接写:
- id: webStartup
config:
port: 8080
你确实改了端口,但你同时也把 !!js 表达式整个干掉了。以后再带 --port 9090 就无效了,因为配置里已经是个纯字面量。
经验法则:能用
--port解决的,就别写死到 patch 里。如果必须写 patch,优先只覆盖你真正需要改的行,保留原 bundle 的表达式语义。
五、用 --dump-config 对齐“我以为”和“真实生效”
dsh 提供了两个 dump 命令,专门用来排查配置合成结果:
# 只看 bundle 层默认合成结果
$ dsh --profile web --dump-default-config
加上 profile / home / --patch 后的真实生效树
$ dsh --profile web --patch ./extra.yml --dump-config
这两个命令的本质区别见下图:

图3:dsh 参数边界与 dump 可见范围(概念示意图,非运行截图)。左侧说明 launcher flag 与应用参数的边界;右侧说明 --dump-default-config 仅含 bundle 层,而 --dump-config 追加 profile、home、--patch 层。
dump 输出的特点:
- 每行前面会有注释,标注它来自哪个文件、被哪些覆盖层修改过。
!!js表达式保持原样,不会求值。- 没有命中目标的 patch 会输出到 stderr。
- dump 拒绝任何应用参数,比如
dsh --profile web --dump-config --port 8080会报错,因为--port属于 web 应用。
实际排查时,建议先跑 --dump-default-config 确认 bundle 默认值,再跑 --dump-config 确认自己的 patch 有没有意外覆盖掉不该动的东西。
六、插件不只是 npm 包:dsh plugin 的真实语义
很多人看到 dsh 就说“它是基于 npm 的插件系统”。准确地说,dsh plugin 是带 bundle 自动调和的 pnpm 转发器。
6.1 安装插件
# 给名为 tui 的 profile 安装一个社区 UI 插件
$ dsh plugin --profile tui add github:deepseek-harness/turtle-ui
用 pnpm 的 update / remove / why 同样有效
$ dsh plugin --profile tui update
$ dsh plugin --profile tui remove turtle-ui
dsh plugin --profile <name> 会做三件事:
- 如果 profile 不存在,自动初始化(web/headless 从模板初始化,其他名字用
@deepseek-ai/dsh-base)。 - 把后续参数转发给
pnpm,工作目录就是这个 profile 的目录。 - 执行完后,自动扫描所有已安装依赖,把声明了
dsh.bundle.patch的包加入dsh.profile.bundles。
6.2 bundle 自动登记规则
一个 npm 包如果想成为 dsh 的 bundle,需要在它的 package.json 里声明:
{
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
dsh 安装完会自动检查这个字段,并把它加入 dsh.profile.bundles。如果删除该依赖,下次启动时它也会从层栈中移除。这个设计让 profile 的 bundle 列表始终和 node_modules 里的真实安装状态保持一致。
6.3 git 源码插件的构建拦截
如果你安装的插件是从 git 拉下来的源码包,pnpm ≥ 10 会拦截它的 prepare 脚本,第一次 add 会失败,并提示你:
pnpm allowBuilds: <key>
解决办法:把提示的 key 写进 profile 的 pnpm-workspace.yaml,然后重新执行 dsh plugin --profile <name> add ...。已经构建好的 tarball 或本地 checkout 不会被拦截。
七、把 dsh 塞进 CI:headless 模式实战
dsh 的 headless 模式很容易被忽略,但它其实是把 dsh 接入脚本/CI 的关键入口。
$ dsh --profile headless "run the tests"
它的行为非常明确:
- 创建一个全新的持久化 Agent 会话。
- 把任务文本提交给 Agent。
- 等待任务进入“静默”状态。
- flush 会话。
- 提取最后一条非空的 assistant 文本。
- 如果任务 completed,stdout 输出结果并退出码 0;否则退出码 1。
7.1 为什么适合 CI?
与 Web 模式相比,headless 模式做了大量减法:
- 不启动 ApiProxy、Host、HTTP server、Web runtime、浏览器客户端。
- 成功运行时不写 stderr,也不监听任何端口。
- 退出码语义清晰,
completed= 0,其余 = 1,方便 GitHub Actions / GitLab CI 判断。
7.2 一个 GitHub Actions 示例
name: agent-smoke
on: [push]
jobs:
harness:
runs-on: ubuntu-latest
env:
DSH_HOME: ${{ runner.temp }}/dsh
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: corepack enable
- run: |
npx @deepseek-ai/dsh --profile headless "Run npm test and report any failures concisely"
这个 workflow 里没有 Web UI,没有手动点“同意”,agent 会在 CI 环境里以 workspace-write 权限 preset 执行。注意:dsh 的默认沙箱不限制网络访问和进程可见性,它只把 bash 和文件系统写入限制在工作区内。如果你的 CI 环境敏感,请额外配置 DSH_PERMISSION_MODE。
八、模式选择:标准 / PTC / 极简 / 创造
新建 Web 会话时,dsh 提供了几种运行模式。官方文档没有把它们说得很系统,但根据 CLI reference 和 UI 提示可以整理如下:
| 模式 | 特点 | 适合场景 |
|---|---|---|
| 标准 | 完整编码 Agent,支持文件编辑、shell、搜索、skills、计划、子代理 | 日常开发任务 |
| PTC | 标准模式能力 + Code Mode SDK,让模型用 TypeScript 程序组合多步操作 | 复杂多步骤工作流 |
| 极简 | 只保留 bash 和 str_replace_editor 两个持久工具,固定系统提示 | 跑 benchmark、调试特定能力 |
| 创造 | 标准模式能力 + preset 创作向导与运行时检查 | 设计自定义 Agent preset |
其中“极简模式”对应官方 minimal agent preset,它的配置被设计为最小可复现集合,很适合拿来做基准测试:只给模型两个工具,看它在限定条件下的表现。
DSH_TOOLS_MODE 环境变量还可以在进程级选择 native(内置工具)、code(Code SDK 工具)或 both。
九、避坑清单
- Node 版本不够:dsh 需要 Node 22.19+ 或 24+,旧 LTS 直接启动失败。
- 忘了选 workspace:
npx @deepseek-ai/dsh web启动后,必须在 UI 里选 workspace,否则输入框不可用。 - patch 写死导致 flag 失效:把
!!js表达式改成字面量后,--port等命令行 flag 会失效。 - home 层覆盖 profile:把通用配置写进
$DSH_HOME/cordis.patch.yml会覆盖所有 profile 的同名行。 - telemetry 默认关闭但开启即无脱敏:
DSH_TELEMETRY_MODE=FULL会导出消息文本、工具参数与结果、workspace 路径,没有内置脱敏规则。 - MCP 默认不启用:dsh 内置了 MCP client 支持,但每个 MCP server 命令都是沙箱外的可信可执行代码,默认不启用。
- 源码启动没 build:
pnpm dsh web需要先pnpm run build,否则 module resolution 报错。 --host 0.0.0.0不支持:本地 only,需要反向代理或 tunnel。
十、总结与延伸
DeepSeek Harness 的野心不在于做一个“更好用的 Claude Code”,而在于把 Agent 的每一层都拆成可替换、可审计、可回滚的插件。这种架构带来的好处和风险都很明显:
- 好处:你可以自由组合模型、工具、UI、沙箱策略;官方、社区、私有插件可以在同一个配置层里混用。
- 风险:配置合成顺序和覆盖语义非常反直觉,没搞清楚
cordis.patch.yml的优先级就很容易出现“我以为改了但没用”“命令行 flag 突然失效”这类问题。
掌握下面三个命令,基本就能脱离“只会启动 Web UI”的阶段:
# 看 bundle 默认配置
$ dsh --profile web --dump-default-config
看真实生效配置
$ dsh --profile web --dump-config
在 CI 里跑一次任务
$ dsh --profile headless "你的任务描述"
延伸阅读与官方链接
- DeepSeek Harness 仓库:GitHub - deepseek-ai/deepseek-harness: DeepSeek Harness: Everything is a Plugin. · GitHub
- dsh npm 包:https://www.npmjs.com/package/@deepseek-ai/dsh
- Cordis 插件元框架:GitHub - cordiverse/cordis: Meta-Framework of Spatiotemporal Composability · GitHub
- Cordis 论文:《A Programming Paradigm for Spatiotemporal Composability》
- dsh CLI behavior reference:deepseek-harness/apps/cli/reference/README.md at master · deepseek-ai/deepseek-harness · GitHub
- 官方讨论区:deepseek-ai/deepseek-harness · Discussions · GitHub
注:dsh 当前为开发者预览版,API 与行为可能随版本发生破坏性变更。建议先在独立项目或沙箱环境中试用,再决定是否接入生产工作流。
更多推荐


所有评论(0)