DeepSeek Harness 的 README 很短,直接运行只需要一条命令:
bash
npx @deepseek-ai/dsh web
但如果只是启动页面、填上 API Key,然后把它当成另一个聊天工具,会错过这个项目最有价值的部分。
这篇不重复文档目录,而是走一遍真正的使用路径:启动、配置模型、选择工作区、理解会话,再创建自己的 Profile。
先把项目跑起来
直接体验可以使用 npx:
bash
npx @deepseek-ai/dsh web
需要研究源码或开发插件时,建议使用源码方式:
bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
corepack enable
pnpm install
pnpm run build
pnpm dsh web
当前源码要求 Node.js ^22.19.0 || >=24.0.0。
服务默认监听:
text
http://127.0.0.1:3080
我本地构建时,安装过程会提示 Linux 原生包不支持 macOS,这属于平台选择警告。只要 pnpm run build 和 pnpm dsh --help 正常通过,就不代表安装失败。
页面打开后,为什么输入框不能用
Harness 会把启动命令所在目录作为默认文件系统位置,但 Web UI 不会直接选中它。
需要点击"选择工作区",添加并选中项目目录。没有选中工作区时,输入框会保持禁用。
这个设计看起来多了一步,实际是在明确 Agent 的文件系统作用范围。它不会因为服务从某个目录启动,就默认允许新会话直接操作该目录。
选好工作区后,可以先执行一个只读任务:
text
Summarize this repository and identify its main packages.
确认读取、命令执行和审批链路都正常后,再进行写文件任务。
API Key 保存在哪里
在"设置 → 模型"中添加 DeepSeek API Key 后,模型配置会立即生效,不需要重启服务。
Harness 把凭据和普通配置分开保存:
text
$DSH_HOME/.credentials.yaml
$DSH_HOME/settings.yaml
前者保存凭据,后者保存 Provider、模型和凭据引用。
Web 页面保存成功后只会拿到脱敏描述,不会重新获取明文密钥。这比把 API Key 混在普通 JSON 配置里更适合长期运行。
如果使用环境变量,也可以在启动 Headless Agent 时提供:
bash
DEEPSEEK_API_KEY=your_key \
pnpm dsh --profile headless "summarize this workspace"
真实项目里不要把 Key 写进 Patch、仓库配置或 Shell 脚本。
配置自定义模型时有两个坑
Harness 支持目录 Provider,也支持 OpenAI 兼容网关和自建服务。
自定义 Provider 通常需要:
yaml
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: GATEWAY_API_KEY
api: openai-completions
baseURL: https://gateway.example/v1
models:
- id: my-model
第一个坑是 Provider ID。
Provider ID 会写进会话日志、默认模型和凭据引用。一旦投入使用,就不适合原地重命名。正确做法是新增一个 Provider,再迁移并删除旧配置。
第二个坑是多模态能力。
手动添加的模型默认按纯文本模型处理。如果模型支持图片,需要显式声明:
yaml
models:
- id: vision-model
input: [text, image]
这只是对端点能力的声明,不会验证服务端是否真的支持图片。声明错误后,最终仍会被 Provider 拒绝。
而且图片已经进入会话日志时,仅修改配置未必能解决问题。后续请求可能继续从日志派生出同一附件,此时应开启一个不包含该图片的新会话。
Web 和 Headless 不是两套产品
Web 模式适合交互式使用:
bash
pnpm dsh web
Headless 模式适合自动化任务:
bash
pnpm dsh --profile headless "inspect the repository"
两者共享同一套底层能力,只是加载的 Bundle 不同。
Web Profile 增加了服务器、前端和交互能力;Headless Profile 增加一次性任务运行器,不需要浏览器和服务器。
这也是 Profile 的核心用途:同一套能力可以被组合成不同产品形态。
不要直接改源码,先学会 Patch
假设我们有一个本地插件:
ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply() {
console.log('hello plugin loaded')
}
可以创建一个 cordis.yml:
yaml
- insert:
- id: hello
name: '/absolute/path/to/my-plugin.ts'
然后叠加到 Web Profile:
bash
pnpm dsh web --patch ./scratch-plugin/cordis.yml
这比直接修改内置 Bundle 更适合实验,因为 Patch 只在本次启动中生效。
查看最终结果:
bash
pnpm dsh --profile web --dump-config
--dump-config 很重要。Profile、Home 配置和多个 Patch 同时存在时,不要靠脑子推导最终配置,直接查看组装结果。
创建一套自己的 Profile
如果某组插件需要长期使用,可以创建独立 Profile:
bash
dsh plugin --profile review add your-review-bundle
dsh --profile review --dump-config
dsh --profile review
Profile 会保存在:
text
$DSH_HOME/profiles/review/
├── package.json
└── cordis.patch.yml
package.json 记录依赖和 Bundle 顺序,cordis.patch.yml 保存当前 Profile 的本地覆盖。
删除插件时使用:
bash
dsh plugin --profile review remove your-review-bundle
这样可以维护多套用途不同的 Agent:
text
web 日常交互
headless 自动化任务
review 只读代码审查
writer 文档与内容处理
sandbox 受限命令执行
真正的权限隔离仍然要依靠文件系统、进程和 Sandbox Provider,不能只靠 Profile 名称。
会话不是一组聊天消息
Harness 会把 Turn、Step、模型输出和 Tool 调用写成会话事件。
这会影响几个日常操作:
- 修改默认模型主要影响新会话
- 已发送过请求的会话保留自己的模型记录
- Tool Call 和结果可以回放
- 会话可以恢复或 Fork
- UI 和模型上下文来自同一日志
所以遇到奇怪的历史行为时,不要只看当前 Settings,还要考虑会话日志中已经记录了什么。
权限控制发生在哪里
模型生成 Tool Call 后,不会直接碰文件系统或进程:
text
模型输出 Tool Call
→ tools/pre-execute
→ 权限与审批
→ Tool execute
→ 文件系统或进程 Provider
→ tools/post-execute
→ 写入会话日志
这条链路比"在系统提示词里告诉模型不要乱删文件"可靠得多。
我更建议按用途拆 Profile:
- 阅读代码时只提供读取能力
- 修改代码时再开放写入
- Shell 和网络分别授权
- 不可信仓库使用 Sandbox Provider
- 第三方插件锁定版本或 Commit SHA
模型的自律不是安全边界,Provider 和策略才是。
常用命令速查
bash
# 启动 Web UI
pnpm dsh web
# 查看参数
pnpm dsh web --help
# 查看最终插件树
pnpm dsh --profile web --dump-config
# 临时加载 Patch
pnpm dsh web --patch ./extra.cordis.yml
# 运行一次 Headless 任务
pnpm dsh --profile headless "inspect this repository"
# 安装 Bundle
dsh plugin --profile demo add <package>
# 删除 Bundle
dsh plugin --profile demo remove <package>
上手 DeepSeek Harness 的关键,不是记住多少命令,而是建立一个认识:
你启动的不是固定 Agent,而是一套由 Profile、Bundle、Plugin 和 Patch 组合出来的运行时。
理解这一点后,后面的插件开发就顺了。