DeepSeek Harness 运行形态:一套代码,三种身份
这是 DeepSeek Harness(dsh)系列的第九篇。前八篇把「是什么」「怎么开发插件」「底层怎么转」「日志怎么追溯」「安全怎么兜底」一路聊到了「子 Agent 编排」——这八篇讲的都是它内部长什么样。从这篇开始,我们换个视角,聊它对外长成什么样:一个dsh命令,怎么同时扮演「交互式 Web 应用」「一次性命令行」「JSON-RPC 服务器」三种角色。
这也是它和很多 Agent 框架不一样的地方:SDK 和 ACP 不是单独的二进制,而是 profile。你不需要「装一个 CLI 版、再装一个 SDK 版」,只有一个入口,切个参数就换身份。
一、一个 dsh,五种入口模式
官方文档说得很直白:dsh命令是唯一受支持的 Node 应用启动器。所有形态都从它进来,切换的关键就是--profile参数:
| 命令 | 用途 |
|---|---|
dsh --profile <name> | 启动$DSH_HOME/profiles/<name>下的命名 profile |
dsh --profile acp | 通过 ACP stdio 服务自动化客户端,直到断开 |
dsh --profile headless "job" | 跑一个一次性持久会话,打印最终答案后退出 |
dsh --profile sdk | 通过 JSON-RPC stdio 服务 SDK 客户端,直到关闭 |
dsh --profile sdk-minimal | 用独立的极简 agent 树服务 SDK 客户端 |
dsh web | --profile web的别名 |
dsh plugin --profile <name> <pnpm args> | 把 profile 的插件管理转发给 pnpm |
其中web、headless、sdk、sdk-minimal、acp这五个 profile 会在首次使用时从内置模板自动初始化;其它自定义 profile 必须先通过dsh plugin创建。
二、三种形态各干什么
把上表收敛一下,日常打交道最多的是三种形态:
web:交互式 Web UI,带浏览器界面,适合「坐在机器前」的日常开发。它启动 HTTP 服务器 + WebSocket,前端和 Agent 跑在同一个进程里。headless:一次性任务。dsh --profile headless "run the tests"会新建一个持久会话,把任务跑完、打印最终答案、然后退出。适合 CI、脚本化、无人值守。sdk/sdk-minimal:把 Harness 当作一个 JSON-RPC 服务器,供你自己的程序调用。区别是sdk是完整树,sdk-minimal是只有极简 agent 树的独立版本(下一篇 SDK 集成会重点讲)。
一个容易被忽略的细节:启动器只解析它自己的 flag,其余参数原样转交给被启动的 profile。所以:
dsh--profileweb--port8080# --port 属于 web 应用,不是启动器的dsh--profileheadless"run the tests"dsh--profileweb--help# 这是 web 应用的 help,不是启动器的dsh--help# 这才是启动器自己的 help第一个它不认识、且不是--profile开头的 token,之后的所有东西就都算「应用参数」了。这个设计让每个形态可以有自己的参数空间,互不打架。
三、profile 的本质:一堆 patch 层的堆叠
「profile 就是有序的插件 bundle patch 层,叠在用户自己的覆盖层之下」——这是理解整个运行形态的核心。
一个 profile 目录里就两样关键东西:
package.json:既装 out-of-tree 的插件依赖,又带一个dsh.profilemanifest,里面是有序的bundles列表和patchReload生命周期;cordis.patch.yml:用户自己的 patch 层。
真正跑起来时,整棵树是这样在空 root 上逐层叠出来的:
dsh.profile.bundles里每个 bundle 的 patch,按声明顺序;- 然后是 profile 自己的
cordis.patch.yml; - 再是 home 级的
$DSH_HOME/cordis.patch.yml; - 最后是
--patch命令行传入的覆盖层。
bundles 里名字的解析也有优先级:先从 dsh 安装目录找内置 bundle(@deepseek-ai/dsh-base、dsh-web-app、dsh-headless、dsh-sdk-app、dsh-sdk-minimal、dsh-acp-app),再落到 profile 自己的node_modules(pnpm 装进去的 out-of-tree 插件)。
四、patchReload:改完配置要不要重启
patchReload控制 patch 文件怎么生效,两个取值:
live:监听 profile 和 home 级的 patch 文件,改了立刻重载;startup:只在启动时应用一次。
开发插件时live能省掉反复重启的功夫,改完 patch 直接看效果;生产环境用startup更稳,避免运行中途被外部改动的文件影响。
五、不启动也能看配置
想确认某个 profile 到底组装成了什么样、某一行配置被谁覆盖成了什么值,不用真的把它跑起来。两个命令能「只看不跑」:
dsh--profilesdk-minimal --dump-default-config# 看内置默认配置dsh--profilesdk-minimal --dump-config# 看叠加你所有覆盖后的最终配置后者尤其有用:当你搞不清「我的 patch 到底覆盖成功没有」,--dump-config会直接给出最终生效的树,一目了然。
六、避坑清单
- 别把形态当独立软件装。SDK、ACP 都是 profile,
dsh是唯一入口。再看到「装个 sdk 二进制」的说法,多半是把它和别的框架搞混了。 --port这类参数放对位置。启动器只认自己的 flag,应用参数要跟在第一个非启动器 token 之后,否则会被启动器当成非法选项报错。- 自定义 profile 要先创建。只有
web/headless/sdk/sdk-minimal/acp会从模板自动初始化,别的名字必须先dsh plugin建,否则启动失败。 - 层叠顺序别记反。越靠后的层覆盖力越强:bundle → profile → home →
--patch。想临时改一个值,--patch最方便;想持久改,写 home 级 patch。 - 生产用
startup,开发用live。live会让运行中的进程响应外部文件改动,生产环境里这是个风险点。
小结
这一篇把dsh的「对外身份」讲清楚了:一个入口、五种模式、三种常见形态,背后全是同一个「profile = patch 层堆叠」的模型。你只要记住——换形态不是换软件,是换 profile,就抓住了它的运行形态。
三种形态里,最值得开发者深挖的是sdk:它把 Harness 变成一个 JSON-RPC 服务器,让你能把自己的程序变成「能调用 Agent」的应用。下一篇(第十篇)我们就专门讲这个——怎么用 Python 和 TypeScript 把 Harness 嵌进你自己的应用里。
参考:deepseek-ai/deepseek-harness 官方仓库apps/cli/README.md。