一切皆插件的 Agent 框架,从安装到上手,再到自己写插件
一切皆插件的 Agent 框架,从安装到上手,再到自己写插件
副标题:DeepSeek 官方开源 Agent 框架(dsh)保姆级中文教程
版本:2026 年 8 月 · 对应 dsh 开发者预览版(0.1.x)
2026 年 8 月 13 日晚,DeepSeek 官方在 GitHub 上开源了 DeepSeek Harness(dsh)——一个"一切皆插件"的智能体(Agent)框架。
接下来的 48 小时里发生了什么?
dsh-plugin 标签的插件仓库攒到 288 个(据 36 氪报道);媒体把它称作"Agent 界的 Android"。社区在狂欢,评测在刷屏,新的插件像雨后春笋一样冒出来。但热闹归热闹,真正能让你接住这波流量的,是你比大多数人更早、更系统地学会它。
市面上现有的中文资料要么是新闻稿,要么是零散的安装笔记,还没有一本"从安装到写插件、从 Web UI 到 Python SDK"的完整教程。这本书就是来补这个缺口的。
读完这本书,你会:
书中所有命令都标注了运行环境。代码块可以直接复制。凡是标注"示例/示意"的内容,请在理解后改写为你自己的实现——毕竟,这个框架最擅长的就是让你把"你自己的实现"接进去。
最后提醒一句:dsh 目前是开发者预览版,官方明确说"未来会出现破坏兼容性的变更"。这意味着两件事:一是现在学,你就是第一批吃螃蟹的人;二是版本升级时,多留意官方 Release 和迁移说明。
好,我们开始。
DeepSeek Harness(简称 dsh)是 DeepSeek AI 官方开发并开源的智能体框架(agent harness)。
"harness" 在英语里原意是"马具、挽具",工程语境下指"把动力接出来、把缰绳握在手里"的那层装置。放在 AI 里,harness 就是模型和世界之间的那层"接线":它决定模型能看到什么、能调用什么工具、能改哪些文件、能跑什么命令、以及每一步怎么被记录和审计。
换句话说,模型本身是一台"发动机",而 harness 是把发动机装进车架、接上方向盘、油门和仪表盘的那套系统。DeepSeek Harness,就是 DeepSeek 官方为自家模型(尤其是 DeepSeek V4 系列)打造的一套开源"车架"。
2025 年以来,Claude Code、OpenAI Codex、Cursor 等产品已经证明了"AI 编程智能体"的价值:给模型一个工作区,它就能读代码、跑命令、改文件、自动修复测试。但这类产品大多是封闭的成品——你能配置它,但很难真正改造它。
DeepSeek Harness 把这件事反过来做了:
这对普通开发者的意义是:你不再等官方给你加功能,你可以自己加。 想要一个"给 agent 加知识库检索"的工具?写个插件。想要把文件系统换成远程沙箱?换个 provider。想要在模型请求前注入上下文?监听一个事件。
为了避免误解,把边界说清楚:
把 dsh 放进坐标系里看,更容易理解它的位置:
| 维度 | Claude Code / Codex | DeepSeek Harness |
|---|---|---|
| 定位 | 面向任务的成品工具 | 面向改造的框架(harness) |
| 可扩展性 | 支持 MCP、skill 等扩展 | 一切皆插件,连循环本身都可换 |
| 模型 | 绑定自家/指定模型 | 自带 DeepSeek 适配器,支持 OpenAI 兼容端点 |
| 数据与控制 | 平台管理 | 本地运行,日志、凭据、配置全在本地 |
| 适合谁 | 想马上干活的人 | 想理解、定制、自动化的人 |
注意:这不是"谁更好"的对比,而是"分工不同"。很多人的真实路径是——先用成品工具体验,再用 dsh 搭自己的自动化流水线,甚至把自己的插件反过来分享给别人。
三个理由:
下一章,我们把"一切皆插件"这句话拆开,看看它的五个核心概念。
"一切皆插件"听上去像口号,但它在 dsh 里是有具体含义的。这一章介绍五个绕不开的概念,懂了它们,后面所有章节都会顺理成章。
在 dsh 里,插件是一个导出 apply 函数的 TypeScript 模块。框架加载插件时调用 apply(ctx),把上下文对象 ctx 交给你,你通过 ctx 注册能力:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) {
// 在这里注册能力:工具、事件监听、定时器……
}
这就是一个完整插件的骨架。模型适配器是插件,工具是插件,会话日志是插件,Web UI 是插件,连"智能体循环"本身都是插件。没有需要打补丁的特权内核——扩展 dsh 的方式,就是把插件挂载到其他插件旁边。
插件有三个关键特性:
tools、llm 等服务时,用 inject 声明,框架会等服务就绪再加载你的插件。ctx 注册的一切(事件监听、定时器、工具)在插件卸载时自动撤销。需要手动清理的资源用 ctx.effect() 声明。单看一个插件不够,你得知道"怎么把一堆插件装到一起、分发给别人"。
组合包(bundle)是一个附带一层配置的 npm 包。它的 package.json 里用 dsh.bundle 字段声明自己贡献的 patch 文件:
{
"name": "dsh-hello-plugin",
"version": "0.1.0",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
cordis.patch.yml 就是它要插入的配置层。别人把这个 bundle 装进自己的 profile,你贡献的插件行就生效了。
一句话记住:插件是能力,bundle 是"能力包"的快递盒。
Profile 是存放在 Harness home($DSH_HOME/profiles/<name>)下的具名装配,回答的问题是:"这套配置由哪些 bundle 按什么顺序组成?"
它包含:
package.json:树外插件依赖 + dsh.profile 清单(有序的 bundles 列表);cordis.patch.yml:你自己的 patch 层,在每个 bundle 层之后应用。发行版内置两个模板 profile:
web:完整 Web UI(别名 dsh web);headless:一次性运行器,无服务器。其余 profile 通过 dsh plugin 命令创建和维护,一般不需要手写 manifest。
运行中的 dsh 是一棵由多层 patch 叠加而成的插件树。生效配置按以下顺序逐层应用(后层覆盖前层,按行覆盖):
dsh.profile.bundles 所列各 bundle 的 patch(按列表顺序);cordis.patch.yml;$DSH_HOME/cordis.patch.yml(机器级共享偏好);--patch <path> 覆盖层(按参数顺序)。每条 patch 按 id 定位某一行,替换其整个 config 值(不是深合并)。想看自己机器上实际组合出来的配置树:
dsh --profile web --dump-config
打印出来的任何一行,理论上都可以被你的 patch 替换。这就是"一切皆插件"落地的关键机制:改配置就是改代码,改代码就是改配置。
一个 seam(能力接缝) 是一项可替换能力,由三种角色组成:
举个例子:文件系统。本地提供方让你在本地改文件;把 provider 换成远程沙箱(官方示例里有 E2B 的 POC overlay),Bash、PTY、LSP 会一并搬过去,因为它们共享同一个执行世界。Consumer 的代码一行不用改。
这就是"换一个提供方就能改变整个产品"的原因,也是 seam 和普通"接口"的区别:单一角色不是 seam,把定义、实现、使用三者一起设计才是。
在 dsh 里,事件不只是"通知",它们是架构意义上的扩展点。分三类:
| 事件域 | 作用 | 何时用 |
|---|---|---|
| 会话事件 | 追加到日志的持久事实 | 需要重载后仍然存在的数据 |
Agent 事件(agent/*) | 携带活跃 Agent:inbox、步骤、状态、请求 | 观察或拦截进行中的工作 |
| 能力事件 | 向 fs/*、tools/*、telemetry/* 附加策略 | 给已有能力加策略和适配器 |
Cordis(dsh 底层的插件框架)为事件提供四种分发模式:
emit:观察,监听器按注册顺序执行;waterfall:中间件,可以包装或短路(next() 委托下游);parallel:并行扇出;serial:按序执行并传值。你会经常打交道的是 agent/* 和 tools/* 系列事件。比如 agent/pre-step 决定模型这一轮看到什么,agent/request 可以在每次模型请求前替换推理档位,tools/pre-execute 可以给工具执行加审批策略。
很多新手在这里绕晕,用一张表钉死:
| 概念 | 回答的问题 | 载体 |
|---|---|---|
| 插件 | 我能贡献什么能力? | TypeScript 模块(apply(ctx)) |
| Bundle | 我分发什么? | npm 包 + dsh.bundle patch |
| Profile | 这套东西由什么组成、怎么启动? | $DSH_HOME/profiles/<name> 目录 + dsh.profile |
agent/* 和 tools/* 是最常用的两个域。概念讲完了,下一章动手——用一行命令把 dsh 跑起来。
先确认两件事:
^22.19.0 或 >=24.0.0。在终端跑 node -v 查看版本,不满足就去 nodejs.org 下载 LTS 以上版本。小提示:如果你在国内网络环境,npm 源可能慢。可以设置镜像源(如 npmmirror)后重试,教程本身不需要科学上网。
安装 Node.js 后,打开终端执行:
npx @deepseek-ai/dsh web
首次运行会下载 dsh 包(npx 会提示是否安装,选 yes)。启动成功后,终端会打印访问地址,默认是:
http://127.0.0.1:3080
用浏览器打开它,你会看到 dsh 的 Web UI。
可以全局安装,以后直接敲 dsh:
npm install -g @deepseek-ai/dsh
dsh web
开发者预览期迭代很快,很多人选择源码运行:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
源码运行适合两类人:想改插件的,以及想第一时间跟进官方提交的。
进入 设置 → 模型,找到 DeepSeek 卡片,粘贴你的 API Key 并保存。
关于安全,三个要点:
$DSH_HOME/.credentials.yaml,不会上传;点击 选择工作区,添加你启动 dsh 时所在的目录(或任意你想让 agent 干活的项目目录),然后选中它。
注意:选中工作区之前,会话输入框是不可用的。这是设计,防止 agent 在没明确工作区时乱跑。
新建一个会话,输入类似这样的任务:
Summarize this repository and identify its main packages.
也可以换成中文:
总结这个仓库,列出它的主要包和各自的职责。
你会看到 agent 开始:读取文件 → 制定计划 → 调用工具 → 汇报结果。当操作在当前权限策略下需要审批时,Web UI 会弹窗问你。点允许,它就继续。
把你手头任何一个项目目录作为工作区,试试这些任务:
你会发现 dsh 的行为模式:先规划,再动手,边做边记录,遇到权限边界停下来问你。这就是"harness"和"聊天机器人"的区别——它有工作区、有工具、有审批、有审计日志。
| 现象 | 原因与处理 |
|---|---|
node -v 版本过低 | 升级到 Node 22.19+ 或 24+ |
| 端口 3080 被占用 | 换个端口:dsh web --port 8080(注意 --port 属于 web 应用参数,要放在 dsh 自己的参数之后) |
提示 MISSING_CREDENTIAL | 还没配置 API Key,去 设置 → 模型 保存 |
提示 UNKNOWN_MODEL | 选一个已配置的模型,或检查自定义提供方的模型列表 |
| 输入框不可用 | 还没选中工作区 |
| 国内网络下载慢 | 配置 npm 镜像源后重试 |
npx @deepseek-ai/dsh web 一行启动,默认地址 http://127.0.0.1:3080;下一章,我们离开图形界面,看看命令行和无头模式怎么玩。
Web UI 适合人机交互。但真正让 dsh 值钱的,是它可以被脚本和 CI 调用。这一章讲 dsh 命令本身。
dsh 是启动器,它只解析自己的 flag,把剩下的参数交给 profile 里的应用插件解析。一句话:launcher 的参数在前,应用的参数在后,遇到第一个启动器不认识的部分,就开始算应用的。
dsh --profile web --port 8080 # --port 属于 web 应用
dsh --profile headless "run the tests"
dsh --profile web --help # 打印 web 应用帮助
dsh --help # 打印启动器自己的帮助
| 命令 | 作用 |
|---|---|
dsh web | 启动 Web UI(--profile web 的别名) |
dsh --profile headless "任务" | 无头模式:跑一个一次性会话,打印最终回复后退出 |
dsh --profile <name> | 启动任意命名 profile |
dsh plugin --profile <name> <pnpm 参数...> | 管理 profile 的插件(转发给 pnpm) |
dsh --profile web --dump-config | 打印组合后的配置树(不启动) |
dsh --dump-default-config | 打印默认配置 |
无头模式是脚本化的核心。它接受一个任务文本,创建并持久化一个全新会话,跑完后打印 agent 的最终文本回复并退出:
dsh --profile headless "fix the failing test in this workspace"
注意:
web 和 headless 两个 profile 首次使用会自动初始化;--patch 加载你自己的配置这是开发插件的日常命令。你写了一个插件和对应的 patch 文件,不想装进 profile,只想临时试一下:
dsh web --patch ./scratch-plugin/cordis.yml
多个 patch 可以叠加,按参数顺序应用。用 --dump-config 可以先看组合结果再启动。
dsh plugin:管理 Profile 的插件前面说过,profile 的 manifest 不需要手写,dsh plugin 帮你维护:
dsh plugin --profile demo add ./hello-plugin # 安装本地插件
dsh plugin --profile demo add github:you/hello-plugin # 从 GitHub 安装
dsh plugin --profile demo remove dsh-hello-plugin # 移除
dsh plugin 本质上是在 profile 目录里转发 pnpm 命令,所以 add、remove、install 等 pnpm 子命令都可用。注意它只管理 profile 的依赖和 bundle 层,不会动你的全局环境。
无头模式 + 退出码 + 会话日志,这三样组合起来就是一个完整的 agent 流水线:
# 示例:让 dsh 在 CI 里修测试并留档
DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }} \
dsh --profile headless "inspect this repo, run the tests, fix failures, report what you changed"
进阶玩法:
--patch 按环境切换配置(比如 CI 里换更严格的沙箱策略);dsh 是启动器,先写 launcher flag,再写应用参数;dsh --profile headless "任务" 是无头执行的核心命令;--patch 让你临时叠加配置,--dump-config 让你预览配置树;dsh plugin 管理 profile 的插件,底层转发 pnpm;下一章:Python SDK——不通过命令行,直接在程序里驱动 Harness。
命令行适合"跑一次",Python SDK 适合"在程序里编排"。它让你在自己的 Python 代码里创建 harness、跑任务、拿结果,就像调用一个普通库。
特别注意:官方明确说明 Python SDK 暂不支持 Windows agent——因为底层持久化 PTY 后端需要 POSIX 终端环境。Windows 用户请用 WSL、Linux 服务器或容器来跑 SDK 示例。
克隆仓库(拿内置示例),建虚拟环境,安装 SDK:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk
好消息:安装后的运行时不需要系统再装 Node.js,SDK 自带同版本内置运行时。
export DEEPSEEK_API_KEY=sk-your-key-here
# 如果走 OpenAI 兼容代理,而不是默认官方端点:
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
# export DSH_MODEL=deepseek-v4-flash
# export DSH_SYSTEM_PROMPT='You are a helpful software engineer assistant.'
如果模型不是默认 DeepSeek 官方端点提供的,务必设置 DEEPSEEK_BASE_URL。
仓库自带的 examples/jsonrpc-agent/minimal.py 是对 SDK 的轻量包装,针对隔离的工作区和会话目录跑一个任务:
python examples/jsonrpc-agent/minimal.py \
--workspace /absolute/path/to/workspace \
--session-root /absolute/path/to/sessions \
--session-id example-001 \
"Inspect the repository and fix the failing tests."
脚本会打印 agent 的最终回复。同时,会话目录会收到 JSONL 格式的日志——里面完整记录了组装后的模型请求和工具调用。这对调试和审计非常有用。
from pathlib import Path
from deepseek_harness import DeepSeekHarness
config = Path("examples/jsonrpc-agent/minimal.cordis.yml").resolve()
workspace = Path("/absolute/path/to/workspace").resolve()
sessions = Path("/absolute/path/to/sessions").resolve()
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-v4-flash",
max_tokens=49_152,
cwd=str(workspace),
session_root=str(sessions),
cordis=str(config),
) as harness:
result = harness.run(
"Inspect the repository and fix the failing tests.",
session_id="example-001",
)
print(result.final_response)
几个关键点:
DeepSeekHarness 会延迟启动内置运行时并持续复用,直到退出上下文管理器;cwd 决定 agent 可访问的工作区,session_root 决定会话日志存哪。看明白 minimal 组合里有什么,你就知道"最小 harness"长什么样:
| 属性 | 值 |
|---|---|
| 系统提示词 | DSH_SYSTEM_PROMPT,缺省为 "You are a helpful software engineer assistant." |
| 模型 | --model → DSH_MODEL → 默认 deepseek-v4-flash |
| 面向模型的工具 | 仅持久 bash 与 str_replace_editor |
| Bash 超时 | 300 秒 |
| 编辑器输出上限 | 16,000 字符 |
| 上下文压缩 | 关闭 |
| 文件系统 | 裸本地后端(编辑器用绝对路径) |
| 会话持久化 | session_root 下未压缩的 JSONL |
| 沙箱 | danger-full-access——只能在可丢弃的 checkout 或容器里跑 |
注意最后一行的警告:这个最小组合没有任何沙箱限制,Bash 和编辑器能改运行时进程可见的任何路径。生产环境请换上更严格的策略。
把上面的模式套进循环,就是一个最简单的批量自动化:
from pathlib import Path
from deepseek_harness import DeepSeekHarness
tasks = {
"repo-a": "Run the test suite and fix failures.",
"repo-b": "Find TODO comments and turn them into issues.",
"repo-c": "Update the dependency versions in package.json.",
}
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-v4-flash",
cwd="/work",
session_root="/sessions",
cordis=Path("minimal.cordis.yml").resolve(),
) as harness:
for repo, task in tasks.items():
result = harness.run(task, session_id=repo)
print(f"--- {repo}: {result.final_response[:200]}")
每个 repo 用独立 session id,彼此状态互不污染;/sessions 下留下每次任务的完整 JSONL 日志,方便事后排查。
pip install deepseek-harness-sdk,自带运行时,Python 3.10+;DeepSeekHarness 上下文管理器 + harness.run(task, session_id) 是最小 API;danger-full-access,批量任务务必用隔离环境。下一章进入重头戏:插件开发。
这一章全程动手。目标:创建一个插件 → 加载进 Web UI → 再给它加一个能被模型调用的工具。整个流程在 15 分钟内可以走完。
插件开发建议从官方仓库的源码 checkout 开始(这样能用 pnpm dsh 和内置的 build 环境):
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
然后在仓库根目录建一个实验项目:
mkdir -p scratch-plugin/src
创建 scratch-plugin/src/my-plugin.ts:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
console.log('[hello-plugin] plugin loaded!')
}
这就是一个完整插件:导出 name 和 apply(ctx)。框架加载时调用 apply,把上下文 ctx 交给你。
--patch 覆盖层创建 scratch-plugin/cordis.yml。注意插件路径必须是绝对路径,把 /absolute/path/to/deepseek-harness 换成你自己的:
- insert:
- id: hello
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
用覆盖层启动 Web UI:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
打开 http://127.0.0.1:3080,你应该在启动终端的日志里看到:
[hello-plugin] plugin loaded!
恭喜,你的第一个插件已经跑起来了。
通过 ctx 注册的任何东西——事件监听、工具、定时器——在插件卸载时都会被自动清理,你不用手动 removeListener 或 clearInterval。
如果需要手动清理的资源(比如一个网络连接),用 ctx.effect() 声明:
import type { Context } from '@deepseek-ai/cordis'
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => {
console.log('heartbeat')
}, 5000)
// 返回的函数在插件卸载时执行
return () => clearInterval(timer)
})
}
如果插件要用其他服务(如工具注册表 tools、模型层 llm),声明 inject,框架会等服务就绪再加载:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-tool-plugin'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(/* ... */)
}
把 scratch-plugin/src/my-plugin.ts 替换成下面这个"问候工具":
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'greet-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet',
description: 'Greet someone by name.',
parameters: {
name: { type: 'string', required: true, description: 'The name to greet' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
return `Hello, ${args.name}!`
},
}))
}
拆解一下这个 DSL:
parameters:声明工具入参,defineTool 会根据它推导并校验 args 的类型;output.schema:声明工具返回值的规范形式;output.render:把规范值转换成面向模型的内容;execute:真正的执行逻辑。重启开发命令(如果还在运行):
pnpm dsh web --patch ./scratch-plugin/cordis.yml
在 Web UI 里输入:
Use the greet tool to greet Ada.
模型会调用 greet,并收到 Hello, Ada! 这个工具结果——一个能被模型自主调用的工具就诞生了。
函数形式之外,还有对象形式和类形式:
// 对象形式
export default {
name: 'my-plugin',
inject: ['tools'],
apply(ctx: Context) {
// ...
},
}
// 类形式:需要向其他插件提供服务时使用
import { Service, type Context } from '@deepseek-ai/cordis'
export default class MyService extends Service {
static inject = ['tools']
constructor(ctx: Context) {
super(ctx, 'myService')
}
}
大多数场景函数形式就够了;类形式意味着你的插件本身是一个服务,其他插件可以依赖它。
apply(ctx) 的 TS 模块,通过 ctx 注册能力;--patch 覆盖层是开发期加载插件的最快方式(路径要绝对路径);ctx 的注册自动清理,ctx.effect() 处理手动资源;inject 声明服务依赖,框架保证顺序;defineTool 是写面向模型工具的标准姿势:入参 schema + 执行 + 渲染。下一章,让插件学会"接受配置"。
写死的插件没人愿意用。这一章让你的插件接受 cordis.yml 里的配置,并理解 dsh 的热重载机制。
导出 Config 类型和同名的 Schemastery schema,默认值直接写在 schema 里:
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
export const name = 'my-plugin'
export interface Config {
greeting: string
maxRetries: number
verbose?: boolean
}
export const Config: Schema<Config> = Schema.object({
greeting: Schema.string().default('Hello'),
maxRetries: Schema.number().default(3),
verbose: Schema.boolean().default(false),
})
export function apply(ctx: Context, config: Config) {
console.log(config.greeting) // 用户传的值,或 schema 默认值
}
要点:不要导出普通对象作为 Config,它不满足 Cordis 要求的 Standard Schema 接口,框架会校验失败。
在 scratch-plugin/cordis.yml 的插件行里加 config:
- insert:
- id: hello
name: './src/my-plugin.ts'
config:
greeting: 'Hi there'
maxRetries: 5
插件加载时,Cordis 通过导出的 schema 校验配置,缺的字段自动填默认值。
需要强约束的场景,用 Schema 表达约束本身:
export const Config = Schema.object({
apiKey: Schema.string().required(),
timeout: Schema.number().default(30000),
mode: Schema.union(['fast', 'accurate']).default('fast'),
})
配置不合法时插件会加载失败并给出明确错误,而不是静默用错误值跑——这正是框架喜欢的"响亮失败"。
dsh 的约定:凡是不同部署可能需要不同值的参数,都必须定义为配置字段。
// 错误:硬编码超时
const TIMEOUT = 30000
// 正确:可配置,默认 30000
export interface Config {
timeoutMs: number
}
检验标准一句话:能不能在 cordis.yml 里改这个值,而不需要改代码? 能,就对了。
改 cordis.yml 里某个插件的 config 后,框架会卸载旧实例、加载新实例,不需要重启 dsh。
由于所有注册都是 effect(可逆副作用),替换后不会残留旧实例的注册。开发插件时,这就是你最快的调试循环:改配置 → 自动重载 → 看效果。
Config 类型 + Schemastery schema 是插件的标准配置接口;config 覆盖;下一章:把插件打包成可安装的 bundle,发给别人用。
本地 --patch 只能自己玩。想分享,就得把插件打包成 bundle,装进别人的 profile。
再强调一次(这是最容易绕晕的地方):
package.json 声明 dsh.bundle,回答"这个包贡献什么";$DSH_HOME/profiles/<name> 下描述可启动组合的目录,package.json 声明 dsh.profile,回答"这套配置由哪些 bundle 按什么顺序组成"。bundle 是你编写并分发的东西;profile 是用户启动的东西。没有东西同时是两者。
目录结构:
hello-plugin/
├── package.json # 声明 dsh.bundle
├── cordis.patch.yml # profile 列出本 bundle 时应用的层
└── index.js # 插件模块
package.json:
{
"name": "dsh-hello-plugin",
"version": "0.1.0",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
index.js(插件入口,和第 6 章一样的逻辑):
export const name = 'hello-plugin'
export function apply() {
console.log('[hello-plugin] plugin loaded!')
}
cordis.patch.yml(和 --patch 一样的 patch 数组,区别是插件行按包名引用,Node 才能解析到已安装的代码):
- insert:
- id: hello
name: dsh-hello-plugin
小知识:没有 dsh.bundle 声明的包也能装,但只是普通依赖,不会激活任何层。库类包(供其他插件 import)就用这种格式。
在包含 hello-plugin 的目录里:
dsh plugin --profile demo add ./hello-plugin
首次使用会初始化 profile(自动把 @deepseek-ai/dsh-base 作为第一个 bundle),pnpm 链接你的 checkout,dsh 检测到 dsh.bundle 后把它追加进 dsh.profile.bundles。
先验证再启动:
dsh --profile demo --dump-config # 可以看到 "# == dsh-hello-plugin" 层
dsh --profile demo
移除同样简单:
dsh plugin --profile demo remove dsh-hello-plugin
生效配置在空根之上按顺序叠加:
dsh.profile.bundles 所列各 bundle 的 patch(按列表顺序);cordis.patch.yml;$DSH_HOME/cordis.patch.yml;--patch <path> overlay。推论(给 bundle 作者的两条铁律):
config,不是深合并;cordis.patch.yml 里覆盖你的行,所以给用户大概率会保留的默认值,把其余交给 schema。pnpm publish
用户 dsh plugin add dsh-hello-plugin 安装的就是预构建代码,不需要任何构建权限。在 pnpm publish 前把 lib/ 构建好即可。
pnpm pack
用户执行:
dsh plugin add ./hello-plugin-0.1.0.tgz
dsh plugin --profile demo add github:you/hello-plugin
坑在于:git 安装拉的是源码,不是构建产物,没有任何环节运行你的 build 脚本,TypeScript 包到手时没有 lib/ 输出,加载会失败。两边各要做一件事:
prepare 脚本(pnpm 在 git 安装后运行),且必须自包含——不能假设旁边有 monorepo checkout;prepare 脚本,首次 add 会失败。dsh 会提示把 pnpm 打印的包键写进该 profile 的 pnpm-workspace.yaml:allowBuilds:
dsh-hello-plugin: true
然后重新 add。
请如实看待 allowBuilds 这项授权:它允许该包的代码在安装时于你的机器上执行,且不在 agent 运行的任何沙箱之内。
github:you/hello-plugin#<sha>,让后续推送无法悄悄改变实际运行的内容;dsh.bundle 的 npm 包;profile = 带 dsh.profile 的可启动目录;dsh plugin --profile <name> add/remove 管理安装;--patch;prepare + allowBuilds,注意安全)。下一章,往深处走:看看"一切皆插件"的架构到底长什么样。
这一章给想深入源码的读者。不用背,当"地图"读即可——遇到具体问题时知道去哪查。
dsh 底层是 Cordis(以 vendor 方式引入的插件框架),它的设计来自论文《A Programming Paradigm for Spatiotemporal Composability》。五个核心概念:
Service 子类;ctx.<key>(如 ctx.tools、ctx.llm、ctx.sessions),其他插件通过 key 查找,而不是 import 具体实现;inject 声明依赖:加载顺序通过服务依赖表达,而非手动编排启动序列;emit(观察)、waterfall(中间件/短路)、parallel(并行)、serial(按序传值);这五条你已经在前面的章节里用过前三条,现在知道它们是框架级约定,不是 dsh 独有的临时设计。
| 包 | 职责 | ctx 键 |
|---|---|---|
core/session | 仅追加的会话事件日志 | ctx.sessions |
core/system-prompt | 提示词片段与工具 schema 组装 | ctx.systemPrompt |
core/tools | 作用域化的工具注册表 + 带把关的执行流水线 | ctx.tools |
core/agent | Agent 接口与活跃 agent 注册表 | ctx.agents |
core/agent-loop | 默认的 agent 循环驱动器 | ctx.agentLoop |
core/scope | 按 agent 划分作用域的注册原语 | 库,无 ctx 键 |
llm/llm | 消息与流式词汇表、适配器 seam | ctx.llm |
注意最后一行:agent loop 本身也是一个可替换的包。这就是"没有特权内核"最有力的证据——连循环都能换。
一次对话的"轮次"是这样流转的(简化版):
turn/start
→ 领取输入
→ 组装提示词片段 + 工具 schema
→ agent/pre-step ← 可以改写或拒绝输入
→ step/start
→ 写入 user/message
→ 从日志推导模型历史
→ agent/request ← 可以替换模型/推理档位
→ llm/stream ← 流式响应
→ assistant 消息
→ 工具调用: tools/pre-execute → tools/execute → tools/post-execute
→ step/end
→ 若还有未完成工作或新输入 → 进入下一个 step
→ agent/turn-stopping
turn/end
关键理解:
agent/pre-step、agent/request、llm/stream 和三个 tools/* 事件是 waterfall(瀑布式)事件,监听器必须调用 next() 才能委托下去;agent/turn-stopping 是 serial 事件,没有 next(),用来决定是否停。dsh 有一条运行时不变式:
模型可见即已记录。 抵达模型请求的一切,都必须能从会话日志重建。
因此:
assistant/chunk 事件保证回放和 UI 保真;对插件作者的意义:别绕过日志偷偷给模型塞上下文——那既违反设计,也会在回放和审计时穿帮。
第 2 章讲过 seam = Service Definition + Service Provider + Consumer。架构上的推论:
| 目标 | 机制 |
|---|---|
| 添加模型提供方 | 在 ctx.llm 注册适配器 |
| 添加面向模型的能力 | 在 ctx.tools 注册;schema 加入提示词组装 |
| 添加 shell 执行 | 注册 ctx.shell 后端 |
| 添加用户命令 | 在 ctx.commands 注册,无需模型轮次即可分派 |
| 添加后台工作 | 在 ctx.jobs 注册,job_* 工具收集/停止 |
| 限制进程 | 用 ctx.sandbox 后端,消费方在启动进程前包装 argv |
| 拦截请求/工具/轮次 | agent/* 或 tools/* 事件 |
| 添加模型可见上下文 | agent.inject(),落到下一次获准的请求 |
| 添加 UI 集成 | 驱动 ctx.agents,从 session/event 渲染 |
| 生成会话标题 | 注册唯一的 ctx.sessionTitle 提供方 |
| fork 活跃会话 | ctx.sessions.fork(source, boundary, childSessionId) |
core/agent-loop——循环本身可替换;next();下一章,三个实战案例,把前面所有知识串起来。
场景:你有一个出问题的仓库,想快速知道 agent 能不能自己定位并修复。
做法(无头模式,适合 CI 和本地一次运行):
dsh --profile headless "run the test suite, analyze the failures, fix them, then rerun to confirm"
看点:
--dump-config 检查它实际用到的配置层。进阶:把这条命令放进 GitHub Actions,PR 时自动让 agent 试修一遍并附上报告。
场景:10 个仓库都要"升级依赖并跑通测试",人工逐个做很费时。
做法(第 5 章批量例子的完整版):
from pathlib import Path
from deepseek_harness import DeepSeekHarness
repos = {
"svc-auth": "Upgrade all dependencies to latest compatible, run tests, fix issues.",
"svc-billing": "Upgrade all dependencies to latest compatible, run tests, fix issues.",
"svc-notify": "Upgrade all dependencies to latest compatible, run tests, fix issues.",
}
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-v4-flash",
cwd="/workspaces",
session_root="/sessions",
cordis=Path("minimal.cordis.yml").resolve(),
) as harness:
for repo, task in repos.items():
result = harness.run(task, session_id=repo)
print(f"### {repo}\n{result.final_response}")
看点:
/sessions 下每个 session 一份 JSONL,出事可以回放"模型到底看到了什么、干了什么";danger-full-access,务必在容器或可丢弃环境里跑。场景:你有一个内部知识库,想让 agent 回答问题时能查文档,而不是瞎猜。
思路:写一个工具插件,execute 里调用你的检索服务(比如向量库或 Elasticsearch),返回命中片段。模型会在需要时自动调用它。
骨架:
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'docs-search'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'search_docs',
description: 'Search internal docs and return relevant snippets.',
parameters: {
query: { type: 'string', required: true, description: 'Search query' },
limit: { type: 'number', description: 'Max results, default 5' },
},
output: {
schema: { type: 'array', items: { type: 'string' } },
render: (_args, value) => [{ type: 'text', text: value.join('\n---\n') }],
},
async execute(args) {
// 在这里调用你的检索服务
return fetchSearch(args.query, args.limit ?? 5)
},
}))
}
关键设计:
output.render 把检索结果渲染成模型可读的文本——这是"模型体验"的落点;tools/pre-execute 事件上做策略。效果:agent 遇到"某某接口怎么用"时,会先 search_docs,再基于检索结果回答,比裸模型的幻觉率低一个量级。
社区里已经有 Loops(auto-research) 类插件:让 agent 自己规划步骤、反复搜索、产出报告。原理其实就是本书第 9 章说的——**在 agent/* 事件上挂一个"续跑"策略**:只要目标未完成且预算未耗尽,就让轮次继续。
想动手的话,从 docs/agent-lifecycle.md 和 docs/subsystems/core.md 读起,那里有续跑机制的完整说明。
下一章:生态、坑和 FAQ——让你少走弯路。
发布两天,GitHub Star 突破 10 万(据公开报道),dsh-plugin 话题下的插件仓库 24 小时内就攒到 288 个。社区里已经能看到这些方向的插件:
官方社区入口:
dsh-plugin 话题,方便被发现。对你意味着什么:现在做插件,竞争者少、被看到概率大;而且生态刚起步,缺什么插件,你就有机会补什么。
官方原话:正在快速迭代,未来将出现破坏兼容性的变更。对策:
官方示例的持久 PTY 后端需要 POSIX 环境。Windows 用户用 WSL、Linux 服务器或容器。
prepare 坑从 GitHub 装 TypeScript 插件时,没有构建产物会加载失败;pnpm ≥10 默认拒绝运行 prepare 脚本。对策在第 8 章:作者给 prepare,用户显式 allowBuilds。只对可信源码授权。
SDK 最小示例和某些组合默认 danger-full-access:Bash 和编辑器能改任何路径。永远在可丢弃的环境里跑,或换上严格的沙箱策略。
API Key 在 Web UI 里是只写的(存 $DSH_HOME/.credentials.yaml)。但脚本和 CI 里,别把 Key 写进代码或提交到 git,用环境变量或密钥服务。
DeepSeek 适配器默认路由是 deepseek-official,默认模型 deepseek-v4-flash / deepseek-v4-pro,默认上下文窗口 100 万 token,输出上限默认 256,000 token,推理档位 off | high | max(默认 high,thinking 默认开启)。需要按部署调这些时,改配置而不是改代码。
Q1:没有 API Key 能玩吗?
能启动界面,但不能跑任务。Key 从 DeepSeek 开放平台申请。
Q2:MISSING_CREDENTIAL 是什么?
模型路由没配好凭据:去设置里存 Key,或提供被引用的环境变量。
Q3:UNKNOWN_MODEL 怎么办?
选择已配置的模型;自定义提供方则需要添加缺失的模型。
Q4:端口被占用了?dsh web --port 8080(--port 是 web 应用的参数,放在 dsh 自己的参数之后)。
Q5:能接别的模型吗?
能。设置里可添加 Anthropic、OpenAI 等目录提供方,或自定义 OpenAI 兼容端点。自定义提供方的 Provider ID 是永久的,改名要新建再删除。
Q6:自定义提供方的视觉模型不认图片?
手动录入的模型默认按纯文本处理;在 $DSH_HOME/settings.yaml 给该模型加 input: [text, image]。
Q7:插件改了配置没生效?
配置变更会触发热重载;但 patch 是整行替换 config,检查你是否重述了目标行的所有键。
Q8:日志在哪?
会话日志是 JSONL(Python SDK 场景在 session_root 下);Key 在 $DSH_HOME/.credentials.yaml;机器级配置在 $DSH_HOME/cordis.patch.yml。
Q9:Windows 能跑 Web UI 吗?
能,Web UI 和源码构建在 Windows 没问题;受限制的是 Python SDK 官方示例。
Q10:想给官方提反馈?
GitHub Discussions;给插件仓库加 dsh-plugin 话题提升被发现概率。
最后一章,给你一张继续深入的地图。
第一周 · 会用
第二周 · 会改
第三周 · 会懂
docs/architecture.zh.md;docs/cordis-tutorial/(官方 Cordis 教程,7 课,按顺序做);docs/subsystems/),比如 tools、session、sandbox。第四周 · 会分享
dsh-plugin 话题;CONTRIBUTING.md。| 想看什么 | 去哪 |
|---|---|
| 架构总览 | docs/architecture.zh.md |
| Cordis 入门 | docs/cordis-primer.zh.md |
| Cordis 动手教程 | docs/cordis-tutorial/(7 课) |
| Agent 生命周期/时序 | docs/agent-lifecycle.zh.md |
| 工具执行流水线 | docs/tool-execution-pipeline.zh.md |
| 能力 seam | docs/capability-seams.zh.md |
| 事件生产/消费映射 | docs/event-producer-consumer.zh.md |
| 配置字段全集 | docs/config-catalog.zh.md(自动生成) |
| 子系统详解 | docs/subsystems/(30+ 个) |
| 实操手册 | docs/cookbook/(加包、加工具、加 LLM 适配器等) |
| 术语表 | docs/glossary.zh.md |
examples/:headless-agent、jsonrpc-agent、acp-agent、mcp-memory、web-schedule 等真实组合;packages/:核心包源码,从 core/ 和 llm/ 读起;apps/cli/:命令行的完整行为参考;native/:沙箱等原生能力(进阶)。awesome-deepseek-harness 类仓库帮你筛生态;DeepSeek Harness 目前还只是 0.1 的开发者预览版。但框架级的东西往往就是这样:先理解它的人,定义它之后的模样。
你现在手里的这本书,就是一张门票。剩下的路,靠你在 apply(ctx) 里一笔一笔写出来。
附录里有命令速查表和术语表,随时翻。
前面 12 章你学了安装、CLI、SDK、插件开发。这一章把它们拧成一台"机器":一个每天自动产出代码变更摘要 + 周报的个人 Agent 工作台。做完它,你就不是"会用 dsh",而是"在用它干活"了。
场景:你在维护一个仓库,希望每天自动生成一份"昨天改了什么、有什么风险点"的摘要,周五再汇总成周报。
成品:
weekly_report(注册一个面向模型的工具);reports/)。mkdir -p agent-workbench/{workspace,sessions,my-plugin/src}
三个目录各司其职:
| 目录 | 用途 |
|---|---|
workspace/ | agent 干活的地方:放一个仓库的克隆(或把现有项目复制进来) |
sessions/ | 会话日志(JSONL),出事可回放 |
my-plugin/ | 你的插件项目 |
安全约定:workspace 必须是"弄坏了也不心疼"的副本。dsh 的 agent 会真实改文件。
创建 my-plugin/src/weekly.ts:
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import Schema from '@deepseek-ai/schemastery'
export const name = 'weekly-report'
export const inject = ['tools']
export interface Config {
author: string
repoName: string
outputDir: string
}
export const Config: Schema<Config> = Schema.object({
author: Schema.string().required(),
repoName: Schema.string().required(),
outputDir: Schema.string().required(),
})
export function apply(ctx: Context, config: Config) {
ctx.tools.register(defineTool({
name: 'weekly_report',
description: 'Generate a markdown weekly report from structured change data.',
parameters: {
dateRange: { type: 'string', required: true, description: 'e.g. 2026-08-10 to 2026-08-16' },
commits: { type: 'string', required: true, description: 'One commit subject per line' },
filesChanged: { type: 'string', description: 'One file path per line' },
riskNotes: { type: 'string', description: 'Anything risky or worth attention' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
const lines = [
`# ${config.repoName} 周报`,
``,
`- 作者:${config.author}`,
`- 周期:${args.dateRange}`,
``,
`## 提交列表`,
args.commits.split('\n').filter(Boolean).map(c => `- ${c}`).join('\n') || '- 无',
``,
`## 变更文件`,
args.filesChanged?.split('\n').filter(Boolean).map(f => `- ${f}`).join('\n') || '- 无',
``,
`## 风险与注意`,
args.riskNotes || '- 无',
]
return lines.join('\n')
},
}))
}
注意两点:
execute 是纯函数:它把结构化数据渲染成 Markdown,不做任何危险操作。收集数据的脏活(跑 git log、统计文件)交给模型用自己的 bash 工具完成——职责分离,插件更安全、更容易测试;Config 走的是第 7 章约定:可调参数全部可配置,不硬编码。创建 my-plugin/cordis.yml(路径换成你的绝对路径):
- insert:
- id: weekly
name: '/absolute/path/to/agent-workbench/my-plugin/src/weekly.ts'
config:
author: '你的名字'
repoName: 'my-project'
outputDir: '/absolute/path/to/agent-workbench/reports'
先看配置树,再启动:
pnpm dsh web --patch ./my-plugin/cordis.yml --dump-config
pnpm dsh web --patch ./my-plugin/cordis.yml
在 Web UI 里发这样一个任务:
Inspect the workspace repo. Run git log --oneline -20 to see recent commits,
then use the weekly_report tool with the last week's data and save the result
to the configured output directory.
模型会自己跑 git、整理数据、调用 weekly_report。打开报告目录,你的第一份 Agent 生成的周报就在那了。
临时 --patch 只适合调试。把它转成 bundle,装进一个叫 workbench 的 profile:
my-plugin/package.json:
{
"name": "dsh-weekly-report",
"version": "0.1.0",
"type": "module",
"main": "src/weekly.js",
"files": ["src/weekly.js", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
main指向的是编译后的 JS 产物(用 tsc/tsdown 从weekly.ts构建),示例中为简洁省略了构建步骤;开发期直接走--patch加载 TS 源码即可。
cordis.patch.yml 里的插件行改成按包名引用:
- insert:
- id: weekly
name: dsh-weekly-report
config:
author: '你的名字'
repoName: 'my-project'
outputDir: '/absolute/path/to/agent-workbench/reports'
安装并验证:
dsh plugin --profile workbench add ./my-plugin
dsh --profile workbench --dump-config
dsh --profile workbench
以后 dsh --profile workbench 就是你的"工作台"——自带周报能力。
agent-workbench/run_daily.py:
from datetime import date, timedelta
from pathlib import Path
from deepseek_harness import DeepSeekHarness
root = Path("/absolute/path/to/agent-workbench")
last_week = f"{date.today() - timedelta(days=7)} to {date.today()}"
task = (
"Inspect the workspace. Use git log to list commits from the last 7 days, "
"then call weekly_report with that data and save the markdown to the "
"configured output directory. Report the file path when done."
)
with DeepSeekHarness(
provider="deepseek-official",
model="deepseek-v4-flash",
cwd=str(root / "workspace"),
session_root=str(root / "sessions"),
cordis=Path("minimal.cordis.yml").resolve(),
) as harness:
result = harness.run(task, session_id=f"daily-{date.today()}")
print(result.final_response)
然后用系统的定时任务(macOS launchd、Linux cron、Windows 计划任务)每天早晨执行:
0 9 * * * cd /absolute/path/to/agent-workbench && /path/to/python run_daily.py >> daily.log 2>&1
三个约定再强调一次:
daily-2026-08-16 这种),任务之间状态不串;dsh 固定版本,升级先看官方 Release;DEEPSEEK_API_KEY),绝不写进代码或 git;danger-full-access 的沙箱策略(参考 docs/subsystems/sandbox.md);sessions/ 和 reports/ 挂进备份。search_docs 工具;examples/web-schedule,把任务接进 dsh 自己的调度;docs/subsystems/subagent.md,让主 agent 委派子任务;dsh plugin add dsh-weekly-report 就能装。--patch 调试 → bundle + profile 固化,是插件进生产的标准路径;到这里,你已经有能力搭建属于自己的 Agent 工作台了。附录里的命令速查和术语表,随时回来翻。
# 一行启动 Web UI(首次会自动下载)
npx @deepseek-ai/dsh web
# 全局安装后直接使用
npm install -g @deepseek-ai/dsh
dsh web
# 源码构建运行
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
| 命令 | 作用 |
|---|---|
dsh web | 启动 Web UI(默认 http://127.0.0.1:3080) |
dsh --profile headless "任务" | 无头模式跑一次性任务 |
dsh --profile web --dump-config | 打印组合后的配置树 |
dsh --dump-default-config | 打印默认配置 |
dsh web --patch ./xxx/cordis.yml | 叠加配置层启动 |
dsh plugin --profile demo add ./pkg | 安装插件包到 profile |
dsh plugin --profile demo remove pkg | 从 profile 移除 |
dsh plugin --profile demo add github:you/pkg | 从 GitHub 安装 |
dsh --help | 启动器帮助 |
dsh web --help | Web 应用帮助 |
python -m pip install deepseek-harness-sdk
export DEEPSEEK_API_KEY=sk-...
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
# export DSH_MODEL=deepseek-v4-flash
| 内容 | 位置 |
|---|---|
| 凭据(只写) | $DSH_HOME/.credentials.yaml |
| 机器级配置 | $DSH_HOME/cordis.patch.yml |
| Profile 目录 | $DSH_HOME/profiles/<name>/ |
| 会话日志 | session_root(SDK)下的 JSONL |
| 术语 | 含义 |
|---|---|
| Harness | 把模型接到世界上的那层"接线系统":工具、文件、命令、日志、审批 |
| dsh | DeepSeek Harness 的命令行名称与缩写 |
| Plugin(插件) | 导出 apply(ctx) 的能力单元,一切皆插件 |
| Context(ctx) | 服务容器,通过 ctx.<key> 访问服务 |
| inject | 插件声明服务依赖的方式 |
| Bundle(组合包) | 带一个配置层的 npm 包,dsh.bundle 声明 |
| Profile | 可启动的具名装配,dsh.profile 声明 bundle 顺序 |
| Patch(补丁层) | 按 id 插入或替换配置行的 YAML 层 |
| Seam(能力接缝) | 可替换能力:定义 + 实现 + 使用 |
| Service Definition | seam 中声明接口的角色 |
| Service Provider | seam 中实现接口的角色 |
| Consumer | seam 中使用能力的角色(通常是面向模型的工具) |
| Agent Loop(智能体循环) | 驱动"模型请求 + 工具调用"的循环,本身也是插件 |
| Turn(轮次) | 零个或多个步骤,从领输入到不再欠工作 |
| Step(步骤) | 一次模型请求 + 它调用的工具 |
| Waterfall 事件 | 中间件式事件,监听器须 next() 委托 |
| 会话事件 | 追加到日志的持久事实 |
| 模型可见即已记录 | dsh 的运行时不变量:模型看到的都必须能从日志重建 |
| Headless(无头模式) | 一次性跑任务并退出的 profile |
danger-full-access | 无沙箱限制的权限配置,只能在隔离环境使用 |
$DSH_HOME | Harness 主目录,存放凭据、配置与 profile |
| Cordis | dsh 底层的插件框架 |
| Schemastery | 插件配置的 schema 定义库 |
| MCP | Model Context Protocol,模型上下文协议 |
| E2B | 远程沙箱提供方(官方 POC 示例) |
| HMR | 热重载,配置变更自动替换插件实例 |
github.com/deepseek-ai/deepseek-harness(文档齐全,中英双语)@deepseek-ai/dshdeepseek-harness-sdkdsh-plugingithub.com/cordiverse/cordisgithub.com/cordiverse/paper)platform.deepseek.com提示:开发者预览期信息变化快,一切以官方仓库为准;本书中与仓库不一致之处,请相信官方。
本附录记录本书写作过程中的真实运行验证,供读者放心参照。
| 项目 | 值 |
|---|---|
| 实测日期 | 2026-08-17 |
| Node.js | v24.19.0(满足官方 >=24.0.0 要求) |
| 包管理器 | pnpm 11.21.0(pnpm dlx 等价于 npx) |
| dsh 包 | @deepseek-ai/dsh(与官方 README 一致) |
pnpm dlx @deepseek-ai/dsh web --port 3099
结果:终端输出 dsh web: http://127.0.0.1:3099;用 curl 访问返回 HTTP 200,页面正常响应。
dsh --help
结果:输出 dsh: boot a DeepSeek Harness profile — an ordered stack of plugin-bundle patch layers under your own overrides.,包含 --profile、--patch、--dump-config、--dump-default-config、web、plugin 等选项与命令,与第 4 章命令表完全对应。
dsh --profile web --dump-config
结果:输出分层配置树,可以看到:
# == @deepseek-ai/dsh-base(llm、session、agent 等基础插件行);# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app;id、name 与 config,正如第 2 章"后层 patch 按 id 覆盖前层"的描述。你的环境只要满足 Node 22.19+ 或 24+,按第 3 章步骤操作即可复现上述结果。如遇差异,优先以官方仓库 README 和 Release 说明为准。