目录
入门篇
- 第 1 章 认识 DeepSeek Harness
- 第 2 章 环境准备与安装
- 第 3 章 第一次运行:Web UI 快速上手
- 第 4 章 核心概念速览
进阶篇
- 第 5 章 模型与提供方配置
- 第 6 章 工作区、会话与数据
- 第 7 章 权限、审批与沙箱
- 第 8 章 内置工具全景
- 第 9 章 CLI 与 headless 自动化
- 第 10 章 Profile 与插件管理
- 第 11 章 Skills 技能系统
- 第 12 章 Python SDK
精通篇
- 第 13 章 Cordis 与插件开发基础
- 第 14 章 开发一个工具
- 第 15 章 服务与依赖注入
- 第 16 章 事件系统
- 第 17 章 LLM 适配器
- 第 18 章 打包与发布插件
- 第 19 章 架构深入:扩展点与事件域
- 第 20 章 综合实战:从零构建一个插件
附录
- 附录 A 命令速查
- 附录 B 常用环境变量
- 附录 C 术语表
- 附录 D 官方资源与 FAQ
第一部分 入门篇
第 1 章 认识 DeepSeek Harness
1.1 它是什么
DeepSeek Harness(命令行工具名为 dsh)是 DeepSeek AI 官方开源的 agent harness(智能体框架)。它不是一个"聊天网页",而是一套让你构建、运行和定制 AI 智能体(Agent)的完整运行时:
- Agent 可以读取和编辑工作区文件、运行 shell 命令、搜索代码、访问网页;
- Agent 可以委派子任务给 subagent、运行工作流、维护计划;
- 你可以在 Web UI 中交互,也可以用命令行做无人值守任务,或用 Python SDK 把它嵌进自己的程序。
它最鲜明的口号是 “Everything is a Plugin”(一切皆插件):模型适配器、工具注册表、会话日志、权限策略,乃至 agent 主循环本身,全部都是插件。这意味着你不需要改框架源码,就能通过配置和插件替换任何一环。
1.2 技术底座:Cordis
Harness 的插件体系由开源框架 Cordis 驱动。Cordis 是一个小型插件运行时,核心约定只有五条:
- 插件是实现能力(Service)的对象,可以是一个带
apply(ctx)的函数、对象或Service子类; - 上下文(
ctx)是服务的容器,服务占据稳定的ctx.<key>(如ctx.tools、ctx.llm); - 通过
inject声明服务依赖,框架保证依赖就绪后才加载你的插件; - 类型化事件用于插件间通信,有
emit、parallel、serial、waterfall等分发模式; - 注册是可逆的副作用,插件卸载时自动清理。
这套约定贯穿全书:入门时你只需要感知它的存在,精通篇会完全展开。
1.3 当前状态与版本
- 开源协议:MIT。
- 版本阶段:开发者预览(v0.1 系列,当前仓库版本
0.1.0-rc.7),破坏性变更随时可能发生。 - 社区:GitHub Discussions 是反馈与提问的官方渠道;插件仓库可打上
dsh-plugin话题便于被发现。
1.4 你能用它做什么
| 场景 | 做法 |
|---|---|
| 个人编程助手 | Web UI 选一个工作区,自然语言提需求,Agent 读代码、改代码、跑测试 |
| 无人值守自动化 | dsh --profile headless "任务描述",一次性跑完并输出结果 |
| 把 Agent 嵌入自己的产品 | Python SDK(JSON-RPC stdio)驱动 |
| 接入自有模型/网关 | 添加自定义 Provider(OpenAI 兼容端点)或编写 LLM 适配器 |
| 扩展能力 | 开发工具插件、服务插件、Skill,甚至替换文件系统/沙箱/子代理提供方 |
第 2 章 环境准备与安装
2.1 环境要求
跑 Web UI 与 CLI 只需要 Node.js:
- Node.js:
^22.19.0或>=24.0.0(官方package.json的 engines 声明); - 包管理器:日常使用不需要;若从源码构建,需要 pnpm(仓库使用
pnpm@11.7.0)。
用 Python SDK 时的额外要求:
- Python 3.10 或更高;
- Git;
- 平台:Linux x64、Linux arm64,或 macOS 14+ 的 arm64(SDK 内置运行时暂不支持 Windows agent)。
2.2 最快路径:一条命令启动
npx @deepseek-ai/dsh web
命令会启动 Web UI,默认地址为 http://127.0.0.1:3080。首次运行会自动初始化 web profile。启动时所在的目录将作为默认文件系统位置;Web UI 里还需要你手动添加并选择一个工作区(见第 3 章)。
2.3 从源码运行
如果你想看文档、跑示例或开发插件,克隆仓库:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
仓库根目录的 docs/ 就是官方文档站点源码,也可以本地跑 pnpm run docs:dev 预览。
提示:本教程写作时已将官方仓库克隆到
D:\code\deepseek-harness-test\deepseek-harness-master,你可以随时查阅其中的docs/、packages/、examples/。
2.4 常见安装问题
| 症状 | 处理 |
|---|---|
npx 找不到包 | 确认 Node ≥ 22.19;必要时 npm install -g @deepseek-ai/dsh 后直接用 dsh |
| 从源码构建报 Node 版本错误 | 升级 Node 到 22.19+ 或 24+ |
pnpm install 卡住 | 确认网络可达 npm registry;国内网络可配置 registry 镜像 |
| 端口 3080 被占用 | Web 应用通常支持 --port 等自有参数,例如 dsh web --port 8080(--port 属于 web 应用而非启动器) |
第 3 章 第一次运行:Web UI 快速上手
3.1 启动与登录界面
npx @deepseek-ai/dsh web
终端会打印访问地址(默认 http://127.0.0.1:3080),浏览器打开即可。全新安装不会选中任何工作区,因此选择工作区之前,会话输入框不可用。
3.2 配置模型
打开设置 → 模型,找到 DeepSeek 卡片,输入 DeepSeek API 密钥 并保存。
几个值得注意的设计:
- 密钥是只写的:保存后页面只会收到脱敏描述符,永远不会把明文密钥回传给你;
- 密钥存储在
$DSH_HOME/.credentials.yaml,settings 里只保留凭据引用; - 模型路由立即生效,不需要重启服务器;
- 之后也可以随时增删提供方、换模型,模型选择器里选中的模型会成为新会话的默认值。
3.3 选择工作区
点击选择工作区,添加你启动 dsh 时所在的项目目录(或任意你希望 Agent 操作的目录),然后选中它。Agent 后续的读写、搜索、跑命令都围绕这个工作区展开。
3.4 发送第一个任务
随便找个仓库,然后发送:
Summarize this repository and identify its main packages.
你会看到 Agent 开始工作:读取文件、运行命令、维护计划。如果当前权限策略要求审批,Web UI 会先询问你(比如某个写操作或高权限命令),你批准后它才继续。
建议的第一个任务也可以更具体,例如:
阅读这个仓库的 README 和目录结构,用中文总结项目用途、技术栈和主要模块,并指出入口文件在哪里。
3.5 新手清单
- Node.js 版本满足要求;
npx @deepseek-ai/dsh web成功启动;- 设置里配置了可用的 API Key;
- 选择了工作区;
- 发送任务并观察 Agent 的"读文件 → 跑命令 → 修改 → 汇报"过程;
- 遇到弹窗审批时,先理解它要做什么再点允许。
第 4 章 核心概念速览
这一章用最小篇幅建立全书共用的词汇表。更精确的定义见附录 C。
4.1 会话(Session)、轮次(Turn)、步骤(Step)
- 会话:一段持久化的对话与工作记录,以事件日志(JSONL)的形式落盘。
- 轮次(Turn):会话对一条已接纳输入的一次"排空"过程:模型与工具反复工作,直到不再欠任何工作为止。
- 步骤(Step):一次模型请求 + 该响应引发的工具执行。一个轮次包含零到多个步骤。
- Round:承载一个轮次的外层策略迭代(例如 Goal Round),Round 计数归策略所有,不是每个轮次都算。
4.2 Agent 与 Scope
- Agent 是运行中的智能体实例;一个活跃的 Agent 就是它自身 scope 的 key。
- scope 是"按 agent 划分的注册单位":工具、提示词片段、监听器要么全局可见,要么归属于某一个 agent。同名时最具体的(scope 内的)会遮蔽全局项——这是为不同 agent 定制 persona 和工具变体的机制。
4.3 工具(Tool)
工具是模型可以调用的能力单元,每个工具都有 name、description 和 JSON Schema parameters。内置工具见第 8 章。
4.4 权限三件套:沙箱模式 + 审批策略 + 权限预设
- 沙箱模式(SandboxMode):
read-only(只读)、workspace-write(可写工作区)、danger-full-access(完全访问)。 - 审批策略(ApprovalPolicy):
ask(问用户)、never(永不询问、一律拒绝)。 - 权限预设(Permission Preset):把"沙箱模式 + 审批策略"打包成具名选项。默认表:
| 预设 | 沙箱模式 | 审批策略 | 适用 |
|---|---|---|---|
workspace-write | workspace-write | ask | 日常使用(标准模式) |
danger-full-access | danger-full-access | never | 可丢弃环境/CI 中的全权模式 |
两者的组合不匹配任何预设时,派生值为 custom(仅展示,不可作为切换目标)。
4.5 Profile、Bundle、Patch、Plugin
- Profile:
$DSH_HOME/profiles/<name>下的具名"可启动组合",由若干 bundle 按顺序叠放而成。web、headless是随发行版提供的模板。 - Bundle(组合包):附带一个配置层的 npm 包,回答"这个包贡献什么配置"。
- Patch(补丁层):按 id 定位配置项并替换其整个
config,或插入新配置项。 - Plugin(插件):导出
apply(ctx)的模块,是能力的最小单位。
4.6 事件的三层含义
文档里"事件"有三类,别混:
- 会话事件:追加到日志、必须持久化的事实(
turn/*、step/*、tool/*、user/message、assistant/*等),通过session/event广播。 - Agent 事件(
agent/*):携带活跃 Agent 的实时扩展点,用于观察/拦截进行中的工作。 - 能力事件(
fs/*、tools/*、telemetry/*等):给某个能力 seam 附加策略和适配器的实时事件。
第二部分 进阶篇
第 5 章 模型与提供方配置
模型配置都在 设置 → 模型 页面完成,修改下一次请求即生效,无需重启。
5.1 配置 DeepSeek 官方模型
打开设置 → 模型,DeepSeek 卡片上输入 API 密钥保存即可。默认 DeepSeek 路由是纯文本的 chat-completions。
5.2 添加目录提供方(Anthropic / OpenAI / 其他)
选择添加提供方,选取 Anthropic、OpenAI 等提供方并输入 API 密钥。已安装的目录会提供端点、协议与模型列表。
注意原生认证类提供方的差异:
| 提供方 | 需要什么 |
|---|---|
| Bedrock | AWS 凭据与区域 |
| Vertex | ADC(Application Default Credentials)项目 |
| Azure | api-version |
| Codex | OAuth |
这些提供方只填 API 密钥字段是配不起来的。
5.3 添加自定义提供方(OpenAI 兼容网关)
公司网关、自建服务器、不在目录里的服务,用添加自定义提供方:
- 填小写 Provider ID(永久标识,请求、已保存会话、模型默认值、凭据引用都依赖它,之后不可改名);
- 填显示名称、基础 URL、API 协议、凭据;
- 至少添加一个模型;
- 保存前可以先在模型目录里点获取可用模型,它会对当前草稿的基础 URL 发起
GET /models探测;选中的候选只更新草稿,保存才落库。
Provider ID 需要改名的正确姿势:添加新提供方,再删除旧的。
5.4 图片输入(模态声明)
自定义提供方的模型默认按纯文本对待,因为框架无从询问端点支持哪些模态。给未声明图片能力的模型附图片,会在发送前被拒绝。手动录入的视觉模型需要在 $DSH_HOME/settings.yaml 中声明 input:
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: GATEWAY_API_KEY
api: openai-completions
baseURL: https://gateway.example/v1
models:
- id: legacy-chat
- id: vision-preview
input: [text, image]
input接受text、image,只作用于该模型;- 整条路由上的模型都收图时,可用
defaultInput: [text, image]作为回退值(默认[text]); - 目录提供方要收窄某模型,写在
modelOverrides下,以模型 id 为键; - 这两个字段是对端点的断言而非检查:声明了端点其实不支持的图片能力,请求会被提供方拒绝。
5.5 常见报错与排查
| 错误/现象 | 原因与处理 |
|---|---|
MISSING_CREDENTIAL | 未配置密钥。到模型页存密钥,或提供被引用的环境变量 |
UNKNOWN_MODEL | 模型未配置。换一个已配置模型,或给自定义提供方补上 |
| 获取可用模型返回 401 | 密钥不对。模型发现走 OpenAI 兼容的 GET /models;不支持该端点的服务请手动输入模型 |
| 图片发送前被拒 | 模型未声明图片模态,给模型加 input: [text, image] |
| 带图请求被提供方拒绝 | 模型声明了端点实际不支持的图片能力,从 input/defaultInput 移除 image,并开启新会话(旧会话日志里残留的附件会反复触发同一问题) |
第 6 章 工作区、会话与数据
6.1 工作区(Workspace)
dsh 进程把启动时所在目录作为默认文件系统位置。Web UI 中可以添加并选择多个工作区;Agent 的读写边界(沙箱的 workspace-write 模式)以会话的不可变 cwd 为根。
6.2 会话与日志
每次会话都有一份持久化的 JSONL 事件日志:模型请求、工具调用、审批、标题等都以事件形式落盘。日志是模型所见上下文的唯一来源(“模型可见即已记录”),因此:
- 会话可以回放、恢复、fork;
- 工具调用结果、审批记录都可以追溯;
- 想给 Agent 加模型可见的输入,必须先有对应的会话事件类型。
6.3 Harness Home($DSH_HOME)
Harness home 默认是 ~/.dsh,可用环境变量 DSH_HOME 覆盖。重要文件:
| 路径 | 内容 |
|---|---|
$DSH_HOME/profiles/<name>/ | 每个 profile:package.json(含 dsh.profile manifest)、cordis.patch.yml(用户 patch 层) |
$DSH_HOME/cordis.patch.yml | home 级 patch 层(所有 profile 共享的机器本地偏好) |
$DSH_HOME/settings.yaml | 用户设置文档(如模型模态声明) |
$DSH_HOME/.credentials.yaml | 凭据(只写、脱敏存储) |
$DSH_HOME/.env | 用户级环境层 |
$DSH_HOME/skills/ | 用户技能目录(其 .system 子目录会被跳过) |
6.4 环境变量加载顺序
产品 CLI 会冻结"继承环境 > 项目 .env > 用户 $DSH_HOME/.env"的环境快照:继承环境优先,.env 文件不覆盖继承值。启动目录的 .env 优先于 harness home 的 .env。
第 7 章 权限、审批与沙箱
这一章解决一个核心问题:如何让 Agent 能干活的同事不越界。
7.1 沙箱模式
沙箱只管控文件系统效果:
| 模式 | 含义 |
|---|---|
read-only | 拒绝一切写(只允许 /dev/null 这类必要 sink) |
workspace-write | 允许在工作区根目录及后端承诺的临时区域下写入 |
danger-full-access | 绕过隔离,直接 spawn 原始 argv |
不同平台的后端:Linux 用 bwrap/Landlock,macOS 用 Seatbelt,Windows 用 ACL 受限令牌。强制完整性分 full/partial:Windows ACL 后端与较老内核 ABI 存在 partial 情形,要求绝对边界的场景需要留意。
7.2 审批策略
| 策略 | 行为 |
|---|---|
ask(默认) | 把问题发给应答者链(Web UI 弹窗、ACP 桥接等),无人应答时 fail-closed 为拒绝 |
never | 确定性拒绝一切询问,适合 CI/无人值守 |
审批结果只有四种:allowed-once(一次性授权)、rejected、cancelled、unavailable;除 allowed-once 外全部按拒绝处理。每次询问都会记一对审计事件(approval/asked / approval/decided),只进日志、不进模型 transcript。
7.3 在 Web UI 中切换权限
UI 提供 Permissions 选择器,对应"权限预设":
workspace-write:可改工作区文件 + 需要时问你(日常推荐);danger-full-access:全权 + 从不询问(仅限可丢弃环境、容器、CI)。
预设只是把两个独立旋钮(沙箱模式、审批策略)打包,实际执行仍然由两个旋钮各自的规范 setter 写入。
7.4 给 Agent 的实用建议
- 第一次跑陌生任务,先用
workspace-write+ask,看清楚每个审批再放行; - 跑不可信代码/脚本,改用
read-only或让代码在容器/沙箱工作区里执行; - CI 或定时任务用
never+danger-full-access时,请把工作区设在可丢弃的 checkout 或容器内; - Windows 上沙箱强制是
partial,不要把 Windows 当绝对隔离边界。
第 8 章 内置工具全景
工具是模型可见的能力单元,其 schema 由系统提示词组装给模型。下表是随产品发布的主要工具(packages/*/tool-*),默认配置下模型看到的名称如下:
| 类别 | 工具名 | 说明 |
|---|---|---|
| Shell | bash | 执行 shell 命令;支持 run_in_background 后台运行 |
| Shell | pwsh | Windows 组合的 PowerShell 方言 |
| 文件 | str_replace_editor | 查看/创建/唯一字面量替换/按行插入 |
| 文件 | read write edit read_image | 文件系统工具族;读写遵循先读后写策略 |
| 搜索 | glob grep | 基于随包 ripgrep 的发现工具,无需宿主机装 rg |
| 委派 | subagent | 委派自包含任务(continuable,可后台) |
| 委派 | subagent_fork | 一次性 fork 子 agent(one-shot) |
| 委派控制 | list_agents send_message interrupt_agent | 管理后台 subagent |
| 子级汇报 | report | 进程内可继续子级向父级汇报(仅子级内部可见) |
| 编排 | workflow | 大规模编排 subagent 的纯 JS 脚本(agent/pipeline/parallel/phase 钩子) |
| 编排 | ralph | 面向不可变目标的前台全新 agent 迭代(Ralph 循环) |
| 任务管理 | todo_write | 会话所有的结构化任务清单(整表替换) |
| 后台任务 | job_list job_output job_kill | 统一管理后台 bash/PTY/subagent |
| Web | web_search web_fetch | 联网搜索与抓取(需要 ctx.web 提供方) |
| 技能 | skill | 按名字加载 skill 指令 |
| 规划 | exit_plan_mode | 规划模式下提交计划、获批后退出规划模式 |
| 代码模式 | run_code | 以 TypeScript 程序调用工具(Code Mode) |
| 提问 | ask_user_question | 暂停工具调用,向用户提 1-3 个问题 |
| 终端 | terminal_open terminal_send terminal_read terminal_list terminal_close terminal_signal | 持久终端工具族(需显式启用) |
| 语言服务 | lsp | 结构化 LSP 查询(需提供方) |
| 会话查询 | session_search session_trace session_event_read session_event_search session_event_trace | 只读会话/事件查询(需显式启用) |
| 计划 | schedule_create schedule_delete schedule_list | 定时任务(需显式启用) |
| 目标 | create_goal get_goal update_goal | 同会话目标管理 |
| 自省 | cordis_define cordis_run cordis_stop cordis_undefine cordis_inspect_* | 动态 Cordis 包管理(需显式启用) |
完整 JSON Schema 见官方 工具 Schema 目录。工具的实际可见集合由你的组合配置决定,未加载的包不会出现在模型目录中。
第 9 章 CLI 与 headless 自动化
9.1 入口模式
| 命令 | 作用 |
|---|---|
dsh --profile <name> | 启动 $DSH_HOME/profiles/<name> 下的具名 profile |
dsh --profile headless "job" | 运行一个全新的持久化会话,打印最终答案并退出 |
dsh web | --profile web 的别名 |
dsh plugin --profile <name> <pnpm args> | 在 profile 目录内转发给 pnpm 管理插件 |
dsh --version / -V | 版本号 |
dsh -h | 启动器自身帮助(未指定 profile 时) |
9.2 启动器参数 vs 应用参数
启动器只解析自己的 flag,第一个不认识的 token 之后全部属于应用:
dsh --profile web --port 8080 # --port 属于 web 应用
dsh --profile headless "run tests" # 任务字符串
dsh web --help # web 应用自己的帮助
dsh --help # 启动器自己的帮助
常用启动器 flag:
dsh web --patch ./extra.yml # 追加一个 patch 覆盖层(可重复)
dsh web --dump-config # 打印组合后的配置树并退出(含用户层与 --patch)
dsh web --dump-default-config # 只打印 bundle 层,不含用户层
--dump-config 打印出的任何条目,都可以由你自己的 patch 按 id 覆盖。调试"某个功能从哪里来"时这是第一件武器。
9.3 headless:无人值守跑一个任务
DEEPSEEK_API_KEY=sk-xxx dsh --profile headless "检查这个仓库,修复失败的测试"
行为特点:
- 接受一个非空任务字符串,创建一个全新持久化会话;
- 执行完打印最终的 assistant 文本并退出;
- 会话日志按 JSONL 持久化,可以回放;
- 首次使用会自动初始化
headlessprofile。
9.4 插件管理命令
dsh plugin --profile demo add ./my-plugin # 安装本地插件包
dsh plugin --profile demo add github:you/hello-plugin
dsh plugin --profile demo remove dsh-hello-plugin
dsh plugin --profile demo why <package> # 查看依赖关系
dsh plugin 会把 pnpm 子命令原样转发到 profile 目录执行。首次使用某个 profile 名时会自动初始化(以 @deepseek-ai/dsh-base 为第一个 bundle)。
第 10 章 Profile 与插件管理
10.1 两个核心概念、两种 manifest
| 概念 | 是什么 | manifest |
|---|---|---|
| Bundle(组合包) | 附带一个配置层的 npm 包,回答"这个包贡献什么" | package.json 中 dsh.bundle 指向 patch 文件 |
| Profile | $DSH_HOME/profiles/<name> 下可启动的组合,回答"由哪些 bundle 按什么顺序组成" | dsh.profile 的 bundles 列表 |
没有东西同时是两者:bundle 是你编写和分发的东西,profile 是用户启动的东西。
10.2 层叠加顺序
生效配置在空根之上按此顺序逐层组合:
- profile 的
dsh.profile.bundles所列各 bundle 的 patch,按列表顺序(先是@deepseek-ai/dsh-base); - profile 自己的
cordis.patch.yml; - home 级
$DSH_HOME/cordis.patch.yml(各 profile 共享); - 每个
--patch <path>overlay,按命令行顺序。
两条重要规则:
- 后应用的层按行胜出;
- patch 替换目标行的整个
config,不做键级深度合并——覆盖时未改的键也要重述。
10.3 一个最小 bundle 长什么样
hello-plugin/
├── package.json # 声明 dsh.bundle
├── cordis.patch.yml # 该 bundle 贡献的配置层
└── index.js # 插件模块
{
"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" } }
}
export const name = 'hello-plugin'
export function apply() {
console.log('[hello-plugin] plugin loaded!')
}
- insert:
- id: hello
name: dsh-hello-plugin
10.4 从 GitHub 安装:构建脚本这道坎
dsh plugin --profile demo add github:you/hello-plugin
git 安装拉的是源码,不会运行 build。因此:
- 作者要提供自包含的
prepare脚本(pnpm 在 git 安装后运行它,产出可直接加载的入口); - 用户要在 profile 的
pnpm-workspace.yaml里显式允许构建:
allowBuilds:
dsh-hello-plugin: true
请如实看待这个授权:它允许该包在安装时于你的机器上执行代码,且不在 Agent 沙箱内。只对源码可信的包授权,并尽量锁定 commit(github:you/hello-plugin#<sha>)。
如果不想让用户做授权,就分发构建产物:发布到 npm(dsh plugin add your-package),或交付 tarball(pnpm pack 后 dsh plugin add ./your-package-0.1.0.tgz)。
第 11 章 Skills 技能系统
Skill 是可选的指令包:给模型提供"遇到某类任务时该怎么做的专业知识/流程",按需加载,不占对话常驻上下文。
11.1 存放位置与优先级
本地提供方按 rank 顺序扫描(rank 越小优先级越高):
| Rank | 来源 | 根目录 |
|---|---|---|
| 100 | project-dsh | <项目根>/.dsh/skills |
| 200 | project-agents | <项目根>/.agents/skills |
| 300 | custom | Config.customSkillDirs 配置 |
| 400 | user-dsh | $DSH_HOME/skills |
| 500 | user-agents | $AGENTS_HOME/skills |
| 600 | bundled | Config.bundledSkillDir(需配置) |
项目根是包含 .git 的最近祖先目录;找不到时用当前 cwd。用户 $DSH_HOME/skills 的 .system 子目录会被跳过。
11.2 Skill 的两种文件形态
skill 名字必须是 kebab-case(^[a-z0-9]+(?:-[a-z0-9]+)*$):
- 目录包:
<name>/SKILL.md - 扁平文件:
<name>.md
示例 .dsh/skills/my-checklist/SKILL.md:
---
description: 发布前按清单逐项检查。
---
当需要发布版本时,按以下步骤操作……
支持的前置元数据:
| frontmatter 键 | 作用 |
|---|---|
description | 简短路由说明,模型目录只展示 name + description |
disable-model-invocation | true 时模型不可调用(仅供人类命令等场景) |
user-invocable | 是否对人类调用面可见(省略默认 true) |
11.3 模型如何使用 Skill
- 会话首次观察到非空 skill 目录时,系统会注入一份持久化的目录消息(只含 name + description,绝不包含正文或路径);
- 模型通过
skill({ name })工具按需加载正文;正文变更会反映到下一次调用,但不会改写已注入的目录; - 提供方读取正文时按调用 agent 的 cwd 解析,因此 skill 可以感知工作区。
11.4 给项目装 Skill 的推荐流程
- 在项目根建
.dsh/skills/(或.agents/skills/); - 每个 skill 一个目录 +
SKILL.md,写清楚触发时机(description/whenToUse)与步骤; - 若涉及脚本/模板,放同目录并让正文引用相对路径;
- 在会话里触发场景,观察目录是否注入、工具是否加载正文。
第 12 章 Python SDK
Web UI 之外的程序化入口。SDK 通过 JSON-RPC stdio 驱动 Harness 子进程,把"跑一个 agent 任务"变成普通 Python 函数调用。
12.1 前置要求与安装
- Python 3.10+、Git;
- Linux x64 / Linux arm64 / macOS 14+ arm64(内置运行时暂不支持 Windows agent);
- DeepSeek 兼容的 API 端点与凭据;
- 一个隔离的 workspace(Agent 可以改文件)。
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。
12.2 设置凭据与运行内置示例
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.'
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."
脚本会打印 assistant 的最终回复;会话目录会收到包含模型请求与工具调用的 JSONL 日志。
12.3 在自己程序中使用
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延迟启动内置运行时,退出上下文管理器时回收;同一 harness + session id 会保留该会话的 Bash 进程状态(工作目录、导出的变量、shell 函数);- 独立任务用新的 session id;只有需要延续同一段对话时才复用;
- 示例组合(
minimal.cordis.yml)的工具只有持久bash与str_replace_editor,Bash 超时 300 秒,编辑器输出上限 16,000 字符,关闭了上下文压缩,沙箱为danger-full-access——只能在可丢弃的 checkout 或容器里跑; - 这个组合是刻意的最小集:没有 skill、没有一次性 bash、没有任务工具、没有压缩。需要更多能力,改用自己的
cordis.yml组合即可。
第三部分 精通篇
第 13 章 Cordis 与插件开发基础
13.1 插件就是函数
在 Harness 中,插件是导出 apply 函数的 TypeScript 模块:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
// 在这里注册能力。
}
这是完整配置——没有框架启动代码。插件描述自己的贡献,cordis.yml 负责组合应用。
13.2 三种插件形态
// 1. 函数形式(最常见)
export function apply(ctx: Context) {}
// 2. 对象形式
export default {
name: 'my-plugin',
inject: ['tools'],
apply(ctx: Context) {},
}
// 3. 类形式(需要对外提供服务时)
import { Service, type Context } from '@deepseek-ai/cordis'
export default class MyService extends Service {
static inject = ['tools']
constructor(ctx: Context) {
super(ctx, 'myService')
}
}
13.3 用 --patch 把本地插件加载进 Web UI
在仓库根目录创建 scratch-plugin/src/my-plugin.ts(内容同 13.1),再创建 scratch-plugin/cordis.yml:
- insert:
- id: hello
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
插件路径必须是绝对路径(patch 只贡献配置,不改 loader 解析模块的基准目录)。启动:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
打开 http://127.0.0.1:3080,终端会打印 [hello-plugin] plugin loaded!。
13.4 生命周期与自动清理
通过 ctx 注册的一切(事件监听、工具、定时器)在插件卸载时自动清理。需要手动释放的资源用 ctx.effect():
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => console.log('heartbeat'), 5000)
return () => clearInterval(timer) // 插件卸载时执行
})
}
13.5 声明依赖
export const name = 'my-tool-plugin'
export const inject = ['tools']
export function apply(ctx: Context) {
// ctx.tools 已就绪
ctx.tools.register(/* ... */)
}
框架保证 apply 执行时注入的服务全部就绪;服务缺失时插件会等待而不是报错。
13.6 配置与 Schema
导出 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 默认值
}
在 patch 里传配置:
- insert:
- id: hello
name: './src/my-plugin.ts'
config:
greeting: 'Hi there'
maxRetries: 5
规则:
- 不要导出普通对象当
Config(不满足 Standard Schema 接口); - 配置在加载时校验,不合法就明确失败,绝不静默跳过;
- 凡是不同部署可能取不同值的参数,都必须做成配置字段——检验标准:能否在
cordis.yml改它而不用改代码? - 配置变更会触发 HMR 热替换:卸载旧实例、加载新实例,注册全部自动清理。
第 14 章 开发一个工具
工具是模型能调用的能力。用 @deepseek-ai/dsh-tools 的 defineTool 定义:
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}!`
},
}))
}
要点:
defineTool根据parameters推导并校验args,execute收到的参数已类型安全;execute返回output.schema声明的规范值,output.render再把它转成面向模型的内容块;inject: ['tools']让 Cordis 等待工具注册表就绪。
启动后让模型调用:Use the greet tool to greet Ada. 模型会调用 greet 并收到 Hello, Ada!。
进阶主题(详见官方 adding-a-tool cookbook):嵌套 schema、规范化输出值、后台工作、策略钩子、Code Mode 与 UI 卡片。
第 15 章 服务与依赖注入
15.1 服务是什么
服务是挂载在 ctx 上的命名能力:ctx.tools(工具注册表)、ctx.llm(LLM 服务)、ctx.agents(Agent 服务)都是服务。任何插件都可以提供服务供他人使用。
15.2 提供服务:Service 类
import { Service, type Context } from '@deepseek-ai/cordis'
export default class MetricsService extends Service {
static inject = ['llm'] // 服务也可以依赖其他服务
constructor(ctx: Context) {
super(ctx, 'metrics') // 'metrics' 就是服务名
}
record(event: string, value: number) {
// ...
}
}
消费方声明 inject = ['metrics'] 后即可 ctx.metrics.record(...)。
15.3 类型声明(声明合并)
declare module '@deepseek-ai/cordis' {
interface Context {
metrics: MetricsService
}
}
15.4 依赖行为
- 必需依赖:
inject声明,服务不存在则插件不加载; - 可选依赖:不声明,用
ctx.get('metrics')在调用处查询(可能为 undefined); - 运行期间必需服务消失 → 依赖它的插件自动 dispose;服务重新出现 → 插件自动重载。
15.5 服务隔离
同一个服务可以有多个实例,不同插件组看到不同实例:
- id: group-a
name: '@deepseek-ai/cordis-plugin-group'
group: true
isolate:
shell: true
config:
- name: '@deepseek-ai/dsh-bash-local'
config:
timeoutMs: 5000
- name: './src/plugin-a.ts'
- id: group-b
name: '@deepseek-ai/cordis-plugin-group'
group: true
isolate:
shell: true
config:
- name: '@deepseek-ai/dsh-bash-local'
config:
timeoutMs: 60000
- name: './src/plugin-b.ts'
plugin-a 和 plugin-b 各自看到自己组内的 Bash 实例,互不影响——这是按 agent/preset 定制能力集合的基础。
第 16 章 事件系统
事件是插件间松耦合通信的核心。选对事件域是大多数扩展的第一次决策。
16.1 四种分发模式
| 模式 | 是否 await | 顺序 | 返回值 |
|---|---|---|---|
emit | 否 | 按注册顺序观察 | 无 |
bail | - | 按顺序,首个非空返回值短路 | 有 |
serial | 是 | 按顺序执行,首个非空值终止后续 | 有 |
waterfall | 否 | 按顺序,每个监听器可包装下游 | 有 |
waterfall 是"环绕中间件":必须调用 next() 传递给下游,不调用即短路整条流水线(这是拦截/网关的设计手段)。
16.2 基本用法
// 监听
ctx.on('event-name', (payload) => { /* ... */ })
// 触发
ctx.emit('event-name', payload)
16.3 类型安全的事件
import '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis' {
interface Events {
'my-plugin/ready': (payload: { id: string }) => void
'my-plugin/check': (input: string) => boolean | undefined
'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
}
}
16.4 Harness 事件命名
- Cordis 事件遵循
namespace/action:agent/step、agent/request、agent/request-error、tools/result、session/event; turn/*、step/*、tool/call、tool/result、compaction/*是持久化会话事件,不是同名 Cordis 事件;要观察它们请监听session/event并检查event.type;- 监听器也是 effect:插件卸载时自动移除。
16.5 示例:工具日志插件
import type { Context } from '@deepseek-ai/cordis'
import '@deepseek-ai/dsh-tools'
export const name = 'tool-logger'
export function apply(ctx: Context) {
ctx.on('tools/result', (exec, result) => {
console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`)
const text = result.content
.map(block => block.type === 'text' ? block.text : '')
.join('')
console.log(`[tool result] ${text.slice(0, 100)}`)
})
}
第 17 章 LLM 适配器
接入新模型提供方 = 实现一个继承 LlmAdapter、实现 stream() 的类。
17.1 最小实现
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'
class MyAdapter extends LlmAdapter {
private apiKey: string
constructor(apiKey: string) {
super()
this.apiKey = apiKey
}
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
// 1. 把 options.messages 转成提供方格式
// 2. 调用流式 API
// 3. 把响应转成 StreamChunk
}
}
export interface Config {
apiKey: string
providers: string[]
}
export const Config: Schema<Config> = Schema.object({
apiKey: Schema.string().required(),
providers: Schema.array(Schema.string()).required(),
})
export const name = 'my-llm-adapter'
export const inject = ['llm']
export function apply(ctx: Context, config: Config) {
const adapter = new MyAdapter(config.apiKey)
ctx.llm.registerAdapter(config.providers, adapter)
}
17.2 StreamChunk 协议
stream() 按协议生成分片:
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
async function* exampleChunks(): AsyncIterable<StreamChunk> {
// 1. 每个内容块先发 block-start
yield { type: 'block-start', index: 0, blockType: 'text' }
// 2. 文本走 text-delta
yield { type: 'text-delta', index: 0, text: 'Hello' }
yield { type: 'text-delta', index: 0, text: ' world' }
// 3. block-end 携带完整块
yield {
type: 'block-end',
index: 0,
block: { type: 'text', text: 'Hello world' },
}
// 4. 工具调用块
yield { type: 'block-start', index: 1, blockType: 'tool-call' }
yield {
type: 'tool-call-delta',
index: 1,
id: CallId('call-123'),
name: 'bash',
argumentsDelta: '{"command":"ls"}',
}
yield {
type: 'block-end',
index: 1,
block: {
type: 'tool-call',
id: CallId('call-123'),
name: 'bash',
arguments: '{"command":"ls"}',
},
}
// 5. usage 必须在 finish 之前
yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }
// 6. finish 必须是最后一个分片
yield { type: 'finish', reason: { kind: 'stop' } }
// 或 { kind: 'tool-calls' } 请求执行工具
}
关键规则:每个 block-start 都有对应 block-end;index 从 0 递增;tool-call-delta 的 argumentsDelta 是原始 JSON 文本增量;finish 最后;usage 在 finish 前。
17.3 在 cordis.yml 中使用
- id: my-llm
name: './src/my-llm-adapter.ts'
config:
apiKey: !!js process.env.MY_API_KEY
providers:
- my-provider
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents:
- id: main
provider: my-provider
model: my-model-v1
17.4 进阶与错误处理
- 覆写
resolveModel(provider, model, signal?)返回确切的提供方/模型身份与可选元数据;异步查询必须响应signal; - 能公布模型选项时覆写
listModels(); - 不支持的字段抛带稳定 code 的
LlmError,不要静默丢弃; - 每个 HTTP 请求合并
attributionHeaders()并传递options.signal; - 完整参考实现:
packages/llm/llm-deepseek/(DeepSeek API)与packages/llm/llm-pi-ai/(Pi AI),对照二者可看到同一套契约在不同 SDK 上的落地。
第 18 章 打包与发布插件
把本地插件变成可安装的 bundle(第 10.3 节的结构),然后:
# 在包含 hello-plugin 的目录中
dsh plugin --profile demo add ./hello-plugin
首次使用会初始化 profile,pnpm 链接该 checkout,并因包声明了 dsh.bundle 而把它追加进 dsh.profile.bundles。先验证再启动:
dsh --profile demo --dump-config # 应看到 "# == dsh-hello-plugin" 层
dsh --profile demo
卸载:
dsh plugin --profile demo remove dsh-hello-plugin
让 bundle 持有自己的命令行参数
定义了可运行应用的 bundle 挂载一个普通提供方插件:
- id: hello-startup
name: 'dsh-hello-plugin/startup'
该插件 inject = ['cmdlineArgs'],用 @deepseek-ai/dsh-cmdline 的 parseCmdline 解析启动器之后的参数,再把自己的服务提供出去。受参数配置的行注入该服务:
- id: my-app
name: '@example/my-app'
inject: [myAppStartup]
config:
port: !!js ctx.myAppStartup.port ?? 8080
这样加应用专属 flag 无需修改启动器。
发布三选一
| 方式 | 用户安装 | 是否需要构建授权 |
|---|---|---|
发布 npm(pnpm publish 时构建好 lib/) | dsh plugin add your-package | 否 |
交付 tarball(pnpm pack) | dsh plugin add ./pkg-0.1.0.tgz | 否 |
| GitHub 直装 | dsh plugin add github:you/hello-plugin | 是(allowBuilds) |
第 19 章 架构深入:扩展点与事件域
19.1 组合树
运行中的 dsh 是一棵插件树,由启动时按序叠加的各层组成。没有需要打补丁的"特权内核":扩展 dsh 的方式就是把插件挂载到其他插件旁边。
核心包一览:
| 包 | 职责 | ctx 键 |
|---|---|---|
core/session | 仅追加的 SessionEvent 日志 | ctx.sessions |
core/system-prompt | 提示词片段与工具 schema 组装 | ctx.systemPrompt |
core/tools | 作用域化工具注册表 + 把关执行流水线 | ctx.tools |
core/agent | Agent 接口、活跃 agent 注册表、agent/* 事件 | ctx.agents |
core/agent-loop | 默认 agent 驱动器 | ctx.agentLoop |
core/scope | 按 agent 划分作用域的注册原语 | 库,无 ctx 键 |
llm/llm | 消息与流式词汇表、适配器 seam | ctx.llm |
19.2 轮次流程(Turn Flow)
turn/start
领取输入 → 组装提示词与工具 schema
→ agent/pre-step(可改写/拒绝)
→ step/start → 追加 user/message → 派生历史
→ agent/request → llm/stream → assistant/* 消息
→ tool/call → tools/pre-execute → tools/execute → tools/post-execute → tool/result
→ step/end(工具要求继续或新输入到达则下一步)
→ agent/turn-stopping
turn/end
agent/pre-step、agent/request、llm/stream、tools/* 是 waterfall 事件,监听器必须 next() 委托;agent/turn-stopping 是 serial 事件。
19.3 能力 Seam:可替换能力的三角色
一个 seam 包含三种角色:
- Service Definition:声明接口的 Cordis 服务(如
ctx.shell); - Service Provider:实现(如
dsh-bash-local、dsh-bash-sandbox); - Consumer:消费方(如
dsh-tool-bash)。
这正是"换一个提供方就换掉整个产品行为"的原因:把文件系统与进程提供方指向远程沙箱,Bash、PTY、LSP 一起搬过去。
19.4 新行为该放哪:扩展点速查
| 目标 | 机制 |
|---|---|
| 添加模型提供方 | 在 ctx.llm 注册适配器 |
| 添加面向模型的能力 | 在 ctx.tools 注册;schema 加入提示词组装 |
| 会话拥有不同能力集合 | 组装 agent preset;服务行用 isolate realm |
| 添加 shell 执行 | 注册 ctx.shell 后端 |
| 添加持久终端 | 注册 ctx.terminals 后端 + dsh-tool-terminal |
| 添加用户命令 | 在 ctx.commands 注册(/command,不走模型轮次) |
| 添加后台工作 | 在 ctx.jobs 注册;job_* 工具负责收集/停止 |
| 添加文件系统访问或策略 | 注册 ctx.fs 提供方,或监听 fs/* 事件 |
| 限制进程 | 使用 ctx.sandbox 后端;消费方 spawn 前包装 argv |
| 拦截请求/工具/轮次 | 使用 agent/*、tools/* 事件 |
| 添加模型可见上下文 | agent.inject(),落到下一次获准请求 |
| 添加 UI/编辑器集成 | 驱动 ctx.agents 并从 session/event 渲染 |
| 添加持久会话状态 | 扩展 SessionEventMap;从日志渲染和回放 |
| fork 活跃会话 | ctx.sessions.fork(source, boundary?, childSessionId?) |
| 把注册限定到单个 agent | 使用该 agent 的 agent.ctx |
第 20 章 综合实战:从零构建一个插件
把前几章串起来,做一个"带配置、带工具、带事件日志、可打包"的完整插件。
20.1 目标
给 Web UI 加一个 note 工具:把模型记下的要点追加到工作区的 NOTES.md,同时用事件日志记录每次调用。配置项:文件名与追加模式。
20.2 插件代码 scratch-plugin/src/note-plugin.ts
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'note-plugin'
export interface Config {
file: string
appendNewline: boolean
}
export const Config: Schema<Config> = Schema.object({
file: Schema.string().default('NOTES.md'),
appendNewline: Schema.boolean().default(true),
})
export const inject = ['tools']
export function apply(ctx: Context, config: Config) {
ctx.tools.register(defineTool({
name: 'note',
description: `Append a note to ${config.file}.`,
parameters: {
text: { type: 'string', required: true, description: 'The note text to append' },
},
output: {
schema: { type: 'object', properties: { lines: { type: 'number' } } },
render: (_args, value) => [
{ type: 'text', text: `Appended to ${config.file} (${value.lines} line(s)).` },
],
},
async execute(args) {
const line = config.appendNewline ? `${args.text}\n` : args.text
// 真实实现应通过 ctx.fs 写入;此处为演示直接使用 Node API。
const { appendFileSync } = await import('node:fs')
appendFileSync(config.file, line)
return { lines: line.split('\n').length - 1 }
},
}))
ctx.on('tools/result', (exec, result) => {
if (exec.name === 'note') {
console.log(`[note-plugin] ${exec.arguments.text}`)
}
})
}
20.3 加载并验证
# scratch-plugin/cordis.yml
- insert:
- id: note
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/note-plugin.ts'
config:
file: 'NOTES.md'
pnpm dsh web --patch ./scratch-plugin/cordis.yml
在 Web UI 发送:Please note down: remember to run the linter before committing. 然后检查 NOTES.md 与终端日志。
20.4 打包成 bundle 并安装
按第 10.3 节的目录结构把插件整理成 note-plugin/,补上 dsh.bundle manifest 与 cordis.patch.yml(把 name 换成包名 dsh-note-plugin),然后:
dsh plugin --profile demo add ./note-plugin
dsh --profile demo --dump-config
dsh --profile demo
20.5 打磨清单
- 所有可调参数进
Config,有默认值; - 无效配置在加载时响亮失败(用 Schema 表达约束);
- 文件写入走
ctx.fs(不要裸用 Node API),让沙箱/权限策略能约束它; - 工具调用失败返回结构化错误,不要吞;
- 发布前
pnpm pack或发布 npm,避免用户被allowBuilds卡住。
附录
附录 A 命令速查
# 启动
npx @deepseek-ai/dsh web # Web UI,默认 http://127.0.0.1:3080
dsh web --port 8080 # 换端口(--port 属于 web 应用)
dsh --profile <name> # 启动具名 profile
dsh --profile headless "task" # 一次性任务,打印结果后退出
# 配置检查
dsh web --dump-config # 打印组合后的配置树
dsh web --dump-default-config # 只打印 bundle 层
dsh --version # 版本
# 插件管理(转发给 pnpm)
dsh plugin --profile demo add <pkg> # 安装
dsh plugin --profile demo remove <pkg> # 卸载
dsh plugin --profile demo why <pkg> # 依赖关系
# 开发(源码 checkout 内)
pnpm install && pnpm run build # 构建
pnpm dsh web --patch ./scratch/cordis.yml # 带本地 patch 启动
附录 B 常用环境变量
| 变量 | 用途 |
|---|---|
DSH_HOME | Harness home,默认 ~/.dsh |
DEEPSEEK_API_KEY | DeepSeek API 密钥 |
DEEPSEEK_BASE_URL | 覆盖默认端点(OpenAI 兼容代理) |
DSH_MODEL | Python SDK 默认模型 |
DSH_SYSTEM_PROMPT | Python SDK 示例组合的系统提示词 |
DSH_BUNDLED_SKILL_DIR | 随包 skill 目录配置的环境变量形式 |
AGENTS_HOME | 用户级 agents skill 目录的基准路径($AGENTS_HOME/skills) |
附录 C 术语表(节选)
- seam:可替换能力 = Service Definition + Service Provider + Consumer 三角色。
- scope:按 agent 划分的注册单位;活跃 agent 即自身 scope 的 key。
- shadowing:scope 内同名注册替换全局注册。
- turn / step / round:轮次 / 步骤 / 外层策略迭代。
- bundle / profile / patch:配置层 / 具名组合 / 覆盖层。
- skill:可选指令包,按需加载。
- Ralph 循环:面向不可变目标、每轮全新子会话的工作流策略。
- Goal / Goal Round:附着在会话上的持久目标及接纳的续行周期。
附录 D 官方资源与 FAQ
官方资源
- 仓库:https://github.com/deepseek-ai/deepseek-harness
- Web UI 指南:
docs/user/guide/index.md - 模型配置:
docs/user/guide/providers.md - Python SDK:
docs/user/guide/python-sdk.md - 插件开发:
docs/user/develop/basic/→framework/→practice/ - Cordis 教程:
docs/cordis-tutorial/(无需 API Key 的动手练习) - 工具目录:
docs/tool-catalog.md;配置目录:docs/config-catalog.md - 架构与子系统:
docs/architecture.md、docs/subsystems/
FAQ
Q:dsh web 和 dsh --profile web 有区别吗?
没有,web 是别名。
Q:改模型配置要重启吗?
不用,模型路由下一次请求生效。改 profile/home 的 cordis.patch.yml 会触发 HMR 热重载;修改 settings.yaml 中的模态声明等,按所属插件的 applies 时机生效。
Q:patch 里覆盖某一行,为什么我的其他配置丢了?
patch 按 id 替换整行 config,不是深合并。覆盖时必须重述该行需要的每一个键。
Q:插件路径必须绝对吗?
本地 patch 里的模块路径必须绝对(或从配置目录可解析);打包成 bundle 后按包名引用。
Q:Windows 上能用 Python SDK 吗?
内置运行时暂不支持 Windows agent(持久 PTY 需要 POSIX 终端),建议在 Linux/macOS 上跑 SDK;Windows 上请用 Web UI/CLI(pwsh 工具)。
Q:为什么模型看不到我新加的 skill?
skill 目录注入发生在会话第一次观察到非空完整目录时;检查目录位置(项目根 .dsh/skills)、kebab-case 命名、SKILL.md 形态、disable-model-invocation,并确认模型有 skill 工具。
Q:GitHub 装的插件加载失败?
大概率是 git 安装没有构建产物:作者需要提供 prepare 脚本,用户需要 allowBuilds 授权(见 10.4),或改用 npm/tarball 分发。
951

被折叠的 条评论
为什么被折叠?



