图:一次运行的 dsh 是一棵插件树,模型适配器、工具注册表、会话日志和 Agent 循环都在树上。
DeepSeek Harness(命令名 dsh)是 DeepSeek AI 开源的 Agent 运行环境。官方仓库对它的介绍只有一句话:Everything is a Plugin——一切皆插件。
入口: GitHub 仓库 · 在线文档 · discussions 。想先跑起来,装好 Node.js 后执行
npx @deepseek-ai/dsh web,Web 界面默认开在http://127.0.0.1:3080。
为什么「一切皆插件」值得单独说
大多数 Agent 工具是「一个应用 + 一堆内置能力」:模型适配、工具注册、会话记录、界面都写在同一套代码里,想换掉其中一环,通常只能改源码,或者等上游多开一个配置项。
dsh 走的是另一条路。它的底座是 Cordis ——插件向共享 context 贡献服务和类型化事件,注册是可回滚的副作用,插件卸载时它带来的东西随之退场。在这之上,dsh 把产品本身的每个部分都做成了插件,包括模型适配器、工具注册表、会话日志,乃至 Agent 循环本身。
官方架构文档里有一句话说得最直接:没有需要打补丁的特权内核。你要扩展 dsh,不是往核心里塞代码,而是在树旁边挂一个插件。
一棵在启动时叠出来的插件树
一次运行的 dsh,是启动时按有序分层组合出来的插件树。这里有两个核心概念:
- profile(配置档案):固定名字的组合,存放在 Harness home 里。它列出要叠加的 bundle,装着自己的树外插件,并保存属于用户的那份
cordis.patch.yml。web、headless、sdk、sdk-minimal、acp都是随包发布的模板。 - bundle(组合包):Cordis 配置行以及所挂载代码的分发格式。它插入的内容,依然能被上层 patch 替换。
叠加顺序从一个空根开始:先按 profile 中列出的顺序叠加各 bundle 的 patch,然后是 profile 自己的 cordis.patch.yml,再是 home 级的 cordis.patch.yml,最后是命令行 --patch 指定的覆盖层。
图:同一套内核之上,分层组合出浏览器界面、一次性任务、SDK 与自动化入口。
dsh-base 是共享的第一层:模型适配器、完整工具集、持久化、沙箱与审批策略、设置、凭据都在这一层;dsh-web-app 在其上加载浏览器应用,dsh-headless 换成不带服务的一次性执行器,dsh-sdk-app 与 dsh-acp-app 分别提供 JSON-RPC 和 ACP 服务。sdk-minimal 是刻意的例外——由一个 bundle 独占一整棵显式的 SDK 树,不叠加 dsh-base。
想知道自己这台机器到底启动了哪些行,可以直接把树打印出来:
dsh --profile web --dump-config
它打印的每一行都可以用一份你自己的 patch 替换掉。自定义 profile 默认开启 patch 热重载;随包的 web profile 是实时的,而 headless、sdk、acp 只在启动时应用一次——一次性任务和 stdio 应用在接管工作之后再替换依赖,会破坏自己的生命周期。
同一套内核,五个入口
dsh 只有一个受支持的 Node 应用启动器:dsh 命令加一个 profile 名字。SDK 和 ACP 都是 profile,而不是另外的公开可执行文件。
dsh web——浏览器界面,也是--profile web的别名,目前最顺手的入口;dsh --profile headless "任务"——跑一个全新的持久化会话,打印最终答案后退出;dsh --profile sdk/sdk-minimal——通过 JSON-RPC stdio 为 SDK 客户端提供服务;dsh --profile acp——通过 ACP stdio 服务自动化客户端,直到断开连接。
启动器只解析自己的参数,其余原样交给被启动的 profile。所以 dsh --profile web --port 8080 里的 --port 属于 Web 应用,而不是启动器本身。Python 运行时 wheel 打包的也是同一个命令:客户端默认以显式的 Harness home 启动 dsh --profile sdk。
能力面:从工具到自我编排
在我自己这台机器上,随包安装的 @deepseek-ai/dsh-* 包大约有 230 个,其中面向模型的工具包就有 20 个。撇开清单,实际用起来比较有辨识度的是这么几类:
- 工具与文件:读写与编辑文件、glob 和 grep 检索、bash(常驻与一次性两种)、PowerShell、后台任务、向用户提问、把产物作为交付物提交出来;
- 编排:子代理委派(一次性或可继续对话)、用一段 JavaScript 脚本扇出大量子代理的 workflow、面向单一不变目标的 Ralph 循环、跨轮次延续的 goal;
- 上下文:按需加载任务说明的技能目录、先出计划再动手的 plan mode、待办清单、自动压缩长会话(
/compact)、token 计量; - 接入与安全:把外部 MCP 服务器的工具注册成原生工具的 MCP 客户端、文件与命令沙箱策略、审批策略、写前先读的文件观察策略、JSONL 会话日志(可选 zstd 压缩)。
有两个细节能看出这套设计的取舍。一是会话日志本身也是插件,所以回放会话可以精确复现当时的对话,而不是「大致还原」。二是 hooks 桥接插件能直接运行你已有的 Claude Code hooks.json:命令钩子会在会话、提示、工具调用、停止这些时机生效,用来拦截、追加上下文或者强制再来一轮,不需要重写。
上线状态与风险
有三点需要说清楚:
- 它还是开发者预览版。 官方明确写着会出现关键性的破坏变更(“THERE WILL BE COMPATIBILITY-BREAKING CHANGES”)。当前 npm 上的
latest是0.1.5-rc.1,仓库标签已经到dsh-v0.1.5-rc.2,迭代速度很快。 - 它默认是要干活的。 官方 SAFETY.md 说明:它被设计为能够执行模型生成的代码与命令、加载第三方插件,并访问你交给它的网络、进程、凭据和文件。沙箱、审批提示和权限控制可以降低风险,但不构成隔离保证,不能作为不可信工作负载的唯一安全控制。官方建议用最小必要权限运行,优先放在一次性的虚拟机或容器里。
- 热度很高,但项目很年轻。 仓库 2026 年 8 月 13 日创建,到 9 月 12 日已经有 22 万+ star、2.6 万+ fork,MIT 协议,TypeScript 实现。生态大多围绕
dsh-plugin这个话题生长,第三方插件仓库已经不少。
我自己的用法是把 dsh 放在一台专用机器上、给它最小必要的权限,重要目录另有备份——这也正是官方推荐的做法。
小结
如果只记一句话:dsh 把 Agent 应用拆成了一棵可叠加、可替换的插件树,模型适配器、工具表、会话日志、Agent 循环都在树上,谁也不比谁更特殊。
好处很具体:想换界面、换模型适配、加一个工具或换掉会话存储,一份 patch 就够了,不必 fork 整个项目。代价也很具体:得先理解分层顺序和 patch 语义,出问题时第一件事是搞清楚「这一行到底来自哪一层」。
最省事的体验路径就是 npx @deepseek-ai/dsh web,跑起来之后再看看 --dump-config 打印出来的那棵树。