系列第 1 篇 · 入门 | 项目定位、架构全景与源码速览
本文基于 dsh v0.1.0-rc.5,API 可能随版本变化,一切以官方仓库与本地源码为准。
摘要
DeepSeek 官方开源的 agent harness(智能体运行框架)DeepSeek Harness(dsh)以"一切皆插件"为架构宣言,基于内嵌(vendored)的 Cordis 框架构建。本文作为系列开篇,回答三个问题:dsh 是什么、为什么"一切皆插件"、二次开发能改什么。你将获得一张完整的架构全景图、一份仓库速览地图,以及一套避免踩坑的认知框架。后续 15 篇将带你从"会用"到"能改"再到"能发布自己的发行版"。
标签:DeepSeek Harness、AI Agent、插件开发、Cordis、开源项目
文章目录
-
- 摘要
- [1. 引言:为什么值得关注 dsh](#1. 引言:为什么值得关注 dsh)
- [2. dsh 是什么:一个 agent harness](#2. dsh 是什么:一个 agent harness)
- [3. 一切皆插件:理解插件树](#3. 一切皆插件:理解插件树)
-
- [3.1 插件是什么](#3.1 插件是什么)
- [3.2 插件树:Profile → Bundle → Patch](#3.2 插件树:Profile → Bundle → Patch)
- [3.3 核心服务一览](#3.3 核心服务一览)
- [4. 架构全景图](#4. 架构全景图)
-
- [一次对话的节奏:turn 与 step](#一次对话的节奏:turn 与 step)
- [5. 仓库速览:一张二开地图](#5. 仓库速览:一张二开地图)
- [6. 能力缝(Capability Seam):三件套思维](#6. 能力缝(Capability Seam):三件套思维)
- [7. 二次开发能改什么:官方扩展点地图](#7. 二次开发能改什么:官方扩展点地图)
- [8. 风险与预期:先管理好"漂移"](#8. 风险与预期:先管理好"漂移")
- [9. 术语速查(开篇用)](#9. 术语速查(开篇用))
- [10. 踩坑与经验(认知篇)](#10. 踩坑与经验(认知篇))
- [11. 总结](#11. 总结)
- [12. 延伸阅读](#12. 延伸阅读)
1. 引言:为什么值得关注 dsh
DeepSeek 最近的开源动作不断。这一次不是模型权重,而是一个 agent harness------一个用来构建、运行、调试智能体的"运行时框架"。它叫 DeepSeek Harness (简称 dsh ),官方仓库在 github.com/deepseek-ai/deepseek-harness。
最吸引我的是它的架构宣言:Everything is a Plugin(一切皆插件)。这个说法在很多项目里是营销话术,但在 dsh 里是字面事实------连模型适配器、工具注册表、会话日志、甚至 agent 主循环本身,都是插件,都可以从配置里换掉。
这意味着什么?意味着二次开发的门槛被设计得很低:你想改 dsh 的任何一个行为,都不需要 fork 后硬改核心代码,而是写一个插件挂进去。这正好是本系列要带你做的事。
在开始之前,先把丑话说在前面:dsh 目前处于 **developer preview(开发者预览)**阶段,版本是 v0.1.0-rc.5,官方在 README 里白纸黑字写着 "THERE WILL BE COMPATIBILITY-BREAKING CHANGES"(必然存在破坏性变更)。所以本系列所有内容都会标注版本号,你在阅读时也要有这个心理预期------后面第 12 篇会专门讲"fork 之后如何管理这种漂移"。
2. dsh 是什么:一个 agent harness
先厘清概念。Agent Harness 和"Agent 应用""Agent 框架"不是一回事:
- Agent 框架(如 LangChain)给你一套组装 LLM 调用的积木;
- Agent 应用(如某个聊天机器人)是组装好的成品;
- Agent Harness 介于两者之间:它提供运行 agent 的完整运行时------会话管理、工具执行、权限审批、持久化、Web UI、多进程,但把"你的 agent 长什么样"完全留给你通过插件/配置决定。
dsh 的官方定位是"open-source agent harness developed by DeepSeek AI"。它开箱即带:
- 一个 Web GUI (默认
http://127.0.0.1:3080),直接npx @deepseek-ai/dsh web就能跑; - 一套 headless 运行方式(一次性任务,无服务器);
- 一个 ACP 自动化服务(Agent Client Protocol,给外部程序调用 agent 用)。
它依赖一个叫 Cordis 的框架。Cordis 是开源社区 cordiverse 维护的插件化框架(著名的 Koishi 机器人框架就基于它)。dsh 没有走 npm 依赖,而是把 Cordis 及其基础库以源码形式 vendor(内嵌)进了自己的仓库 ,重新命名到 @deepseek-ai scope 下(如 @deepseek-ai/cordis,当前版本 4.0.0-rc.7),目的在 vendor/README.md 里写得很清楚:让 harness 完全拥有自己的框架层------可审计、可打补丁、可钉版本。这一点对二次开发者很重要:你改的"框架"和"产品"在同一个仓库里,没有黑盒。
3. 一切皆插件:理解插件树
3.1 插件是什么
在 Cordis 的世界里,插件(plugin)是一个最小的注册单元:它向一个共享的 Context(上下文)贡献服务 、事件 和效果(effect) 。一个典型的 dsh 插件长这样(改编自官方文档 docs/cookbook/adding-a-tool.md 的最小工具示例,简化了参数):
ts
import { readFile } from 'node:fs/promises'
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'my-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'read_file',
description: 'Read a file from disk.', // 模型看到的就是这段描述
parameters: {
path: { type: 'string', required: true, description: 'Absolute path' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args, exec) {
return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
},
}))
}
注意两个关键点:
- 注册是副作用 :插件通过
ctx.tools.register(...)这样的调用产生效果,插件卸载时效果自动逆注册(dispose),不留残留。整个 dsh 都遵循"注册即副作用、卸载即回收"这一纪律。 - 没有特权核心 :上面这段代码注册的工具,和 dsh 内置的 bash、fs、web 工具处于完全相同的地位。dsh 官方文档(
docs/architecture.md)原话是"There is no privileged core to patch"------你要扩展 dsh,不是去改一个特权核心,而是在一堆插件旁边挂一个自己的插件。
3.2 插件树:Profile → Bundle → Patch
一个正在运行的 dsh,是一个插件树(plugin tree)------由启动时按顺序叠加的多个"层"组合而成。理解这三层,是理解 dsh 配置体系(第 5 篇会展开)的钥匙:
| 层 | 是什么 | 例子 |
|---|---|---|
| Profile(档案) | 一个命名的组合,列出它要叠哪些 bundle | web、headless 是官方自带模板 |
| Bundle(包) | 可分发/可安装的 Cordis 配置行 + 代码的打包格式 | dsh-base(基础层)、dsh-web-app(Web 应用层)、dsh-headless |
| Patch(补丁) | 按"行 id"覆盖已有配置或插入新行 | 用户的 cordis.patch.yml、--patch 覆盖 |
层级叠加顺序大致是:profile 列出的 bundle 依次应用 → profile 的 cordis.patch.yml → 用户主目录的 patch → 命令行 --patch 覆盖。每一层都能改掉下面层的任何一行配置。
想亲眼看到你的机器真正 boot 出什么样的树,跑这一条命令(本机实测可用):
sh
dsh --profile web --dump-config
它会打印出完整的配置树------上面每一行都可以被你自己的 patch 覆盖。这是"配置即二次开发"的最轻入口。
3.3 核心服务一览
插件向 Context 贡献的服务 通过 ctx.<key> 访问。以下是 dsh 的核心服务(摘自 docs/architecture.md):
| 服务 | 拥有什么 | ctx 键 |
|---|---|---|
| Session | 追加式会话事件日志与内存存储 | ctx.sessions |
| System Prompt | 提示词分段与工具 schema 组装 | ctx.systemPrompt |
| Tools | 作用域工具注册表与受守卫的执行管线 | ctx.tools |
| Agent | Agent 接口、活动注册表、agent/* 事件 |
ctx.agents |
| Agent Loop | 实现 Agent 接口的默认驱动器(主循环) |
ctx.agentLoop |
| LLM | 消息与流式词汇表 + 适配器缝 | ctx.llm |
注意 ctx.agentLoop:主循环本身也是一个可替换的服务 。这也是"一切皆插件"最极致的体现------你甚至可以换掉 agent 怎么思考的主循环,只要实现同一个 Agent 接口。
4. 架构全景图
把上面的概念拼起来,dsh 的一次典型对话流程大致是:
#mermaid-svg-uPQhdZjCzgmp4xuN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uPQhdZjCzgmp4xuN .error-icon{fill:#552222;}#mermaid-svg-uPQhdZjCzgmp4xuN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uPQhdZjCzgmp4xuN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uPQhdZjCzgmp4xuN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uPQhdZjCzgmp4xuN .marker.cross{stroke:#333333;}#mermaid-svg-uPQhdZjCzgmp4xuN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uPQhdZjCzgmp4xuN p{margin:0;}#mermaid-svg-uPQhdZjCzgmp4xuN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN .cluster-label text{fill:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN .cluster-label span{color:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN .cluster-label span p{background-color:transparent;}#mermaid-svg-uPQhdZjCzgmp4xuN .label text,#mermaid-svg-uPQhdZjCzgmp4xuN span{fill:#333;color:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN .node rect,#mermaid-svg-uPQhdZjCzgmp4xuN .node circle,#mermaid-svg-uPQhdZjCzgmp4xuN .node ellipse,#mermaid-svg-uPQhdZjCzgmp4xuN .node polygon,#mermaid-svg-uPQhdZjCzgmp4xuN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uPQhdZjCzgmp4xuN .rough-node .label text,#mermaid-svg-uPQhdZjCzgmp4xuN .node .label text,#mermaid-svg-uPQhdZjCzgmp4xuN .image-shape .label,#mermaid-svg-uPQhdZjCzgmp4xuN .icon-shape .label{text-anchor:middle;}#mermaid-svg-uPQhdZjCzgmp4xuN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uPQhdZjCzgmp4xuN .rough-node .label,#mermaid-svg-uPQhdZjCzgmp4xuN .node .label,#mermaid-svg-uPQhdZjCzgmp4xuN .image-shape .label,#mermaid-svg-uPQhdZjCzgmp4xuN .icon-shape .label{text-align:center;}#mermaid-svg-uPQhdZjCzgmp4xuN .node.clickable{cursor:pointer;}#mermaid-svg-uPQhdZjCzgmp4xuN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uPQhdZjCzgmp4xuN .arrowheadPath{fill:#333333;}#mermaid-svg-uPQhdZjCzgmp4xuN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uPQhdZjCzgmp4xuN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uPQhdZjCzgmp4xuN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uPQhdZjCzgmp4xuN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uPQhdZjCzgmp4xuN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uPQhdZjCzgmp4xuN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uPQhdZjCzgmp4xuN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uPQhdZjCzgmp4xuN .cluster text{fill:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN .cluster span{color:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uPQhdZjCzgmp4xuN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uPQhdZjCzgmp4xuN rect.text{fill:none;stroke-width:0;}#mermaid-svg-uPQhdZjCzgmp4xuN .icon-shape,#mermaid-svg-uPQhdZjCzgmp4xuN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uPQhdZjCzgmp4xuN .icon-shape p,#mermaid-svg-uPQhdZjCzgmp4xuN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uPQhdZjCzgmp4xuN .icon-shape .label rect,#mermaid-svg-uPQhdZjCzgmp4xuN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uPQhdZjCzgmp4xuN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uPQhdZjCzgmp4xuN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uPQhdZjCzgmp4xuN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 能力层
运行时
用户层
Web GUI / CLI / ACP
Agent Loop(turn/step 驱动)
Session 日志(append-only 事件流)
System Prompt 组装
Tools 注册表 + 执行管线
LLM 适配器缝
shell / fs / web / subprocess / terminal ...
sandbox / approval(策略与审批)
(图 1:dsh 运行时全景。自绘,建议用 drawio 重新绘制后导出 PNG 上传)
几个值得记住的机制:
- 模型可见 ⟺ 已记录 :模型每次请求看到的上下文,都从 Session 日志投影(
deriveMessages())而来。这条不变式意味着:任何想让模型看到的新输入,都必须对应一个新的事件类型------第 9 篇专门讲怎么扩展。 - 事件是扩展点 :会话事件(持久)、agent 事件(
agent/*,拦截进行中的工作)、能力事件(fs/*、tools/*、telemetry/*,给能力缝挂策略和适配器)。 - 瀑布事件要 next() :
agent/pre-step、agent/request、llm/stream和tools/*的监听器是瀑布(waterfall),必须调用next()放行,否则会短路整条链------这是新手最容易写错的点(第 6 篇展开)。
一次对话的节奏:turn 与 step
dsh 里有两个节奏单位:**step(步)**是"一次模型请求 + 它调用的工具";**turn(回合)**是零个或多个 step,从第一个输入被认领开始,到没有欠账为止。典型流程是:
text
turn/start
认领下一步输入 + 一条排队消息
组装提示词分段 + 工具 schema
-> agent/pre-step(可改写或拒绝)
step/start
模型请求(llm/stream)-> assistant/chunk* -> assistant/message
工具调用(tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*)
step/end
工具还欠一次请求,或新输入到达 -> 下一步
turn/end
其中 turn/*、step/*、user/message、assistant/*、tool/* 是持久会话事件 (写进日志、可回放),其余是运行期扩展点,分属三个事件域。第 6 篇会带着断点逐事件走一遍这条流程。
(图 3:turn 序列图。自绘,参考 docs/agent-lifecycle.md 的 sequence diagram 重绘后导出)
5. 仓库速览:一张二开地图
拿到源码后(本系列基于本地克隆,实测环境:Node v24.15.0、pnpm 11.7.0、HEAD 47f943859b,版本 0.1.0-rc.5),顶层结构如下(本机实测目录):
text
D:\ai\deepseek\deepseek-harness
├─ .agents/ # Agent Notes(决策记录)与工作流技能
├─ apps/ # cli(命令行入口)、web(前端构建)
├─ docs/ # 架构/开发/子系统/手册,中英双语
├─ examples/ # 可运行示例:acp-agent、headless-agent、mcp-memory ...
├─ native/ # Landlock 沙箱原生模块
├─ packages/ # 219 个 @deepseek-ai/dsh-* 包(49 个组)
├─ patches/ # pnpm 补丁
├─ python/ # Python SDK
├─ scripts/ # 门禁与生成器
├─ vendor/ # vendored 的 Cordis 框架源码(9 个包)
└─ website/ # 文档网站(VitePress)
(图 2:仓库顶层结构。作者实测 Get-ChildItem 输出整理)
二次开发者最需要熟悉的是 packages/,它按"组"组织,每个组有明确职责(完整清单见 packages/README.md),挑几个重要的:
- core/ :产品 API 骨架------
session、system-prompt、tools、agent、agent-loop、scope; - llm/ :LLM 能力族------抽象服务 + 各 provider 适配器(
llm-deepseek、llm-pi-ai); - shell/ fs/ subprocess/ terminal/ web/:模型面向的执行能力(bash、文件、进程、PTY、网页搜索);
- session/:会话持久化(JSONL/SQLite)、投影、标题、遥测;
- client/ host/:Web GUI 的浏览器端与 HTTP 服务端;
- bundle/:可安装的 profile 补丁层(base/web-app/headless);
- interaction/:审批、权限、命令、ask-user;
- sandbox/:进程沙箱(bwrap/Landlock/Seatbelt/Windows ACL)。
每个包都是标准形态:package.json(@deepseek-ai/dsh-<name>,private: true)、src/(TypeScript)、tests/、README.md。所有包统一走 pnpm workspaces + 双聚合 tsconfig(Host/Client)构建,这带来一个现实:改动任何一个包的源码,都要通过仓库的构建和门禁------第 2 篇会带你把整套构建跑通。
docs/ 是双语文档体系,按层级组织:architecture.md(架构总览)→ subsystems/(每个子系统的类型与 API 参考,50+ 页)→ cookbook/(步骤式指南)→ user/(产品手册)。中文版与英文版成对维护(如 architecture.zh.md),仓库用自动配对与字数门禁保证它们不漂移------你在官方仓库里看到的任何 .zh.md 都不是机器直译的凑数内容。
6. 能力缝(Capability Seam):三件套思维
这是 dsh 架构里最重要的一个概念,值得在开篇就种下(第 7 篇会完整实战)。
一个**能力缝(Capability Seam)**是一个可替换的能力,由三个角色构成:
| 角色 | 职责 | 例子(shell 能力) |
|---|---|---|
| Service Definition(服务定义) | 声明接口、注入键、事件 | dsh-shell 定义 ctx.shell 与请求/规格类型 |
| Service Provider(服务提供者) | 实现该接口 | dsh-bash-local / dsh-pwsh-local(本机执行)、dsh-bash-sandbox / dsh-pwsh-sandbox(沙箱执行) |
| Consumer(消费者) | 使用该接口,通常是模型面向的工具 | dsh-tool-bash(bash 工具) |
关键在依赖方向 :Consumer 只依赖 Service Definition,绝不依赖具体 Provider。所以换一个 Provider,整个产品跟着换------比如把 fs/subprocess 的 Provider 指向远程沙箱,Bash、PTY、LSP 全部随之迁移,不需要改任何 Consumer 代码。
对二次开发者来说,这个思维的价值是:先找缝,再写插件。你想加的能力,大概率有一个现成的缝可以挂(工具缝、LLM 缝、shell 缝、fs 缝......),而不是去改核心循环。
7. 二次开发能改什么:官方扩展点地图
docs/architecture.md 里有一张"Where new behavior goes"表,是二开最权威的起点,我摘录几个高频目标(完整版见官方文档):
| 你的目标 | 机制 |
|---|---|
| 接一个新模型 | 在 ctx.llm 上注册适配器 |
| 加一个模型面向的能力 | 注册到 ctx.tools,schema 自动进入提示词组装 |
| 加 shell / 持久终端执行 | 注册 ctx.shell / ctx.terminals 后端 |
| 拦截一次请求、工具或回合 | 用 agent/* 或 tools/* 事件 |
| 给模型加上下文 | agent.inject(),落进下一次请求 |
| 加持久会话状态 | 扩展 SessionEventMap,从日志渲染和回放 |
| 加 UI / 编辑器集成 | 驱动 ctx.agents,从 session/event 渲染 |
| 给一个会话不同的能力集 | 组合 agent preset(preset 插件) |
配套的官方 cookbook 也值得收藏(都是步骤式指南,本系列会逐个展开):
docs/cookbook/adding-a-package.md、adding-a-tool.md、adding-an-llm-adapter.md、adding-a-conversation-node.md、extension-cookbook.md。
本系列的路线图
把官方地图收进口袋后,说清楚本系列 16 篇怎么带你走完"从入门到精通":
- 入门篇(01--05)· 会用:环境搭建(02)、第一个插件(03)、第一个工具(04)、配置体系(05)。目标:你能让 dsh 跑起来、能挂上自己的插件和工具。
- 进阶篇(06--11)· 能改:核心包与事件流(06)、能力缝三件套(07)、接入新 LLM(08)、会话事件扩展(09)、Web GUI(10)、策略与安全(11)。目标:你能读懂核心源码、按官方范式改 dsh。
- 精通篇(12--16)· 会造:fork 后私有构建与发布(12)、vendor 内核与自修改(13)、会话持久化与检索(14)、测试与门禁(15)、踩坑实录(16)。目标:你能维护自己的发行版。
每一篇都遵循同一条流水线:先用真实源码/文档核实每一个命令、路径与 API(禁止编造),再给出可运行的示例,最后标注哪些输出需要你实跑回填------确保文章里的每一行都经得起你在本地验证。
8. 风险与预期:先管理好"漂移"
先看生态现状,再谈风险。dsh 已经通过 npm 公开发布(官方 README 提供 npx @deepseek-ai/dsh web 一键启动,本仓库最近的提交也正好是 feat/npm-public 与 publish the dsh family publicly),官方有 Discord 社区,也鼓励插件仓库打上 dsh-plugin topic 便于被发现。生态在起势,但远未稳定。
作为开篇,我想把最容易翻车的认知问题讲在前面。dsh 目前的状态决定了二开策略:
- rc 阶段无兼容承诺 :官方明确"backends reject old on-disk formats"------旧格式的磁盘数据会被拒收。SQLite 用单调递增的
SCHEMA_VERSION,dsh-session的SESSION_FORMAT_VERSION保持在 0 且无兼容承诺。你的二开代码要跟着版本走,不要假设 API 稳定。 - 版本漂移管理:建议在 fork 上建立一个自己的基线(tag 或 release 分支),官方上游有更新时再评估合并。这是"能改"和"会造"的分水岭,第 12 篇完整讲。
- vendor 纪律 :
vendor/里的 Cordis 是钉版本的源码副本,改动需要登记到vendor/README.md的 Local modifications 清单,并有 manifest 守卫。能通过插件解决的问题,不要动 vendor。 - 双语文档门禁 :仓库对文档有严格的字数预算、中英配对、死链检查(
doc-sync)。改代码时顺手改文档是仓库纪律,但发博客时注意区分"仓库纪律"和"读者需要"。
9. 术语速查(开篇用)
本文出现的术语都给了英文原名,这里汇总成一张速查表,后面 15 篇会反复用到:
| 术语 | 含义 |
|---|---|
| Plugin(插件) | 贡献服务/事件/效果的注册单元,卸载时效果自动回收 |
| Context | 插件共享的上下文,服务通过 ctx.<key> 访问 |
| Effect(效果) | 注册产生的副作用,随插件卸载逆注册(disposer) |
| Seam(能力缝) | Service Definition / Provider / Consumer 三件套 |
| Profile(档案) | 命名组合,列出要叠加的 bundle |
| Bundle(包) | 可分发的 Cordis 配置行 + 代码 |
| Patch(补丁) | 按行 id 覆盖或插入配置 |
| Waterfall(瀑布事件) | 监听器必须调用 next() 放行的链式事件 |
| Turn / Step | 回合 / 步:一次对话的节奏单位 |
| Harness home | dsh 的用户主目录(profile、补丁、数据;本机实测 DSH_HOME 指向用户目录下的 .dsh) |
10. 踩坑与经验(认知篇)
本篇是认知篇,没有代码坑,但有三个"认知坑",提前排掉:
- 坑 1:把 rc 当稳定版用 。有人照着旧文章配置
cordis.yml,升级后字段失效。解法:写作/阅读一律标注版本;升级前看git log与 release notes;二开代码把版本号写进 README。 - 坑 2:想改行为就改核心 。dsh 的设计就是让你别这么干------先查扩展点表(第 7 节),90% 的需求能落到某个缝或事件上。改
agent-loop意味着你要同步更新架构文档,代价很大。 - 坑 3:忽略"注册即副作用"纪律 。写插件时手动注册却忘了随卸载回收,会导致 HMR(热更新)后重复注册、行为叠加。记住:一切贡献走
ctx.effect()/ctx.on(),注册函数的返回值就是 disposer。
11. 总结
本文你能带走的结论:
- dsh 是 DeepSeek 开源的 agent harness,基于 vendored Cordis,一切皆插件,没有特权核心------扩展 dsh 的方式是挂插件,不是改核心。
- 一个运行中的 dsh 是插件树 :Profile(档案)→ Bundle(包)→ Patch(补丁)三层组合,
dsh --profile web --dump-config能看你的树。 - 核心服务(sessions/tools/agents/agentLoop/llm)都可替换;模型可见 ⟺ 已记录是头号不变式。
- 仓库有 219 个包、双语文档、严格的构建门禁;二开入口在
docs/cookbook/与扩展点表。 - 当前是 rc 阶段、无兼容承诺------先建自己的版本基线,再动手改。
下篇预告 :第 2 篇《DeepSeek Harness 二次开发:源码搭建与首次运行》------带你把环境搭好、构建跑通、Web GUI 亮起来,并亲手用 --dump-config 看清你的插件树。
12. 延伸阅读
- 官方架构文档:
docs/architecture.md(仓库内,改动 packages/ 前必读) - Cordis 入门:
docs/cordis-primer.md - 术语表:
docs/glossary.md - 包清单:
packages/README.md - 扩展 cookbook:
docs/cookbook/extension-cookbook.md - 官方仓库:https://github.com/deepseek-ai/deepseek-harness