01这是什么
DeepSeek Harness(命令名 dsh)跑在 vendored 的
Cordis 依赖注入框架上。
docs/architecture.md 写得很直白:没有特权内核可以打补丁——模型适配器、工具注册表、
会话日志、agent 主循环全部是插件,都能从配置里替换。你扩展它的方式不是改核心,而是在旁边挂一个插件;
所有注册都是可回收的 effect,插件卸载时自动解除。
三个贯穿全局的概念
| 概念 | 含义 |
|---|---|
| Capability seam 能力接缝 |
每个能力必须凑齐三个角色:Service Definition(接口)/ Service Provider(实现)/ Consumer(通常是模型可见的工具)。只有一个角色不叫接缝。换一个 provider 就换掉整个产品行为——比如把 fs 与 subprocess 同时指向 E2B 远程沙箱,Bash、PTY、LSP 会全部跟着搬家,工具代码一行不改。 |
| Profile / Bundle / Patch | 运行时 = 有序叠加的配置层:各 bundle 的 cordis.patch.yml → profile 自己的 patch → home 级 patch → 命令行 --patch 覆盖。后面的层按行 id 覆盖前面的(整块替换 config,不做深合并)。dsh --profile web --dump-config 可以打印真实组装出的插件树。 |
| Model-visible ⟺ logged | 任何进入模型请求的内容必须能从 append-only 的 session log 重建,有运行时不变量断言这一点。所以 fork、resume、回放、遥测、UI 全部是同一条事件流的投影;新增一种模型可见输入就必须新增一个 session 事件。 |
能力接缝:三个角色缺一不可
这是理解整个仓库最省力的一把钥匙。任何一项能力都被拆成三个角色,分别住在不同的包里; 只实现其中一个不叫接缝,添加一项能力意味着三个角色一起设计。
fs 与 subprocess 的 provider 同时指向 E2B,
Bash、PTY、LSP 全部跟着搬进远程沙箱,而 Consumer 侧的工具代码一行不改。
配置层叠:运行时是怎么被拼出来的
dsh 启动时不存在"默认配置文件"这种东西,它从空的插件树开始,按固定顺序叠加补丁层。
后面的层按行 id 覆盖前面的,且是整块替换 config——没有深合并。
dsh --profile web --dump-config 可以打印最终组装结果,且会标注每一行来自哪个文件、被哪些覆盖层改过——
调试组合问题时这是第一个该跑的命令。
主循环的形状
一个 step = 一次模型请求加上它触发的工具调用;一个 turn = 零或多个 step, 在第一份输入被认领前开启,在没有任何欠账时关闭。
turn/*、step/*、user/message、assistant/*、tool/* 是持久化的 session 事件;
agent/pre-step、agent/request、llm/stream 与三个 tools/* 是 waterfall 扩展点。
agent-loop 是整个仓库里唯一含有具体循环逻辑的包,其余一律是挂在扩展点上的插件。
被拒绝或改写为空的首次认领仍然会关闭一个没有花费 step 的持久 turn——这样日志里保留了这次尝试。
02子系统全景
docs/subsystems/ 下有 44 篇子系统参考文档,每篇对应一个能力域。
下面按"平面"把它们归拢,并标出各自在 Cordis 容器上注册的服务键——看 ctx 键就知道谁提供什么、谁能被替换。
标 — 的表示该子系统不注册独立服务键,而是通过事件或既有服务参与。
控制骨架
模型请求的组织者。这一层被改动的代价最高,扩展点也最密集。
| 子系统 | ctx 键 | 负责 |
|---|---|---|
core | ctx.agents / ctx.agentLoop | Agent 接口、活跃注册表、唯一的具体循环驱动 |
session | ctx.sessions | append-only 事件日志与内存存储 |
tools | ctx.tools | 分层作用域的工具注册表与受保护执行管线 |
system-prompt | ctx.systemPrompt | prompt 分节与 tool schema 组装 |
llm-streaming | ctx.llm | 消息与内容块词汇、流式分块协议、适配器接缝 |
token-meter | ctx.tokenMeter | 回放感知的 token 计量 |
scope | — | 按 agent 隔离注册的原语(库,无服务键) |
执行世界
agent 真正"动手"的地方。这一组 provider 换成远程实现,整个执行世界就整体搬家。
| 子系统 | ctx 键 | 负责 |
|---|---|---|
filesystem | ctx.fs | 文件读写接缝与 fs/* 策略事件 |
shell | ctx.shell / ctx.shellEnv | bash / pwsh 执行器接缝与受管环境变量 |
subprocess | ctx.subprocess | 进程树生成与回收 |
terminal | ctx.terminals | owner 隔离的持久 PTY 会话 |
sandbox | ctx.sandbox / ctx.sandboxPolicy | 进程隔离后端与按会话持久化的策略 |
code-runtime | ctx.codeRuntime | 执行模型编写的程序(Code Mode 的后端) |
lsp | ctx.lsp | 语言服务器接缝与 stdio provider |
任务与编排
| 子系统 | ctx 键 | 负责 |
|---|---|---|
subagent | ctx.subagents | 子 agent provider 注册、委派、续跑;后端可为进程内/ACP/Codex/Claude Code |
workflow | ctx.workflowEngine | 模型编写的编排脚本在 worker 线程中执行 |
jobs | ctx.jobs | 与种类无关的后台作业运行时(bash / PTY / subagent 共用) |
goal | ctx.goals | 同会话目标的持久化与生命周期 |
plan | — | 计划模式作为被记录的状态,带评审式退出 |
schedule | — | 会话内的定时跟进 |
invariants | ctx.invariants | 运行时不变量注册与断言(每个包都要提供 ./invariant) |
上下文与知识
| 子系统 | ctx 键 | 负责 |
|---|---|---|
skills | ctx.skills | 技能 provider 注册表与目录合并(本地/打包/远程) |
compaction | ctx.compaction | 上下文压缩策略 |
spill | ctx.spillStore | 超长工具结果外溢到存储,返回可再次读取与检索的定位符 |
session-query | ctx.sessionQuery | 受权限约束的会话检索、血缘、SQLite 全文搜索 |
session-reference | ctx.sessionReferenceResolver | 其他会话的有界快照 |
attachment | ctx.attachments | 持久附件身份、校验、内容寻址存储 |
web | ctx.web | 搜索与抓取的 provider 接缝 |
数据面
| 子系统 | ctx 键 | 负责 |
|---|---|---|
persistence | ctx.sessionPersistence | JSONL / SQLite 持久化后端 |
session-projection | ctx.sessionProjections | 从事件流派生的只读视图 |
session-title | ctx.sessionTitle | 由日志生成会话标题(唯一 provider) |
session-telemetry | — | OTLP 导出,默认关闭 |
storage | ctx.storage / ctx.storageDomain | 非会话存储枢纽与域划分 |
workspace | ctx.workspaceRegistry | 工作区实体 |
settings | ctx.settings | 用户设置接缝与文件后端 |
credentials | ctx.credentials | 凭据引用接缝(env 优先于 .env) |
人机协作
| 子系统 | ctx 键 | 负责 |
|---|---|---|
approval | ctx.approval | 一次性批准决策的协调 |
user-questions | ctx.userQuestions | provider 无关的提问/回答接缝 |
permission-presets | ctx.permissionPresets | 面向用户的权限预设呈现与持久化 |
commands | ctx.commands | 人类命令的注册与派发(不经过模型轮次) |
feedback | ctx.messageFeedback | 人工反馈 |
平台与集成
| 子系统 | ctx 键 | 负责 |
|---|---|---|
web-server | ctx.webServer / ctx.apiProxy | HTTP 路由服务与 API 网关 |
client-modules | ctx.uiSlots 等 | 浏览器半边:外壳、连线、对象服务、插槽 |
typert | ctx.typert / ctx.typertGateway | 类型图生成、产物加载、运行时注册表 |
extensions | ctx.dynamicCordisRunner | agent 自我修改:动态包定义、运行、撤回 |
03包依赖结构
下面的数字是从仓库里 219 个 package.json 的
dependencies 与 peerDependencies 现算的,只统计 @deepseek-ai/dsh-* 内部依赖。
仓库自己也生成一份完整图:docs/module-graph.md(1638 行 mermaid)——那份是全量,这里是能一眼看懂的聚合。
dsh-invariants 被除自己以外的每一个包依赖。
这不是巧合,是仓库规约的硬性要求:每个包都必须提供 ./invariant companion,
注册运行时不变量或写明"为什么没有"。这是全仓库唯一的普遍依赖。
分层与依赖方向
把 49 个包组按依赖方向压成四层后,结构非常干净:依赖只向下流,上层可以随意换,下层几乎不动。
最重的几条跨组依赖边(边权 = 该组内有多少个包依赖到目标组):
| 从 | 到 | 边权 | 说明 |
|---|---|---|---|
client | runtime-diagnostics | 39 | 浏览器半边每个包都带不变量 companion |
bundle | client | 33 | web-app bundle 要把全部 UI 插件装进树里 |
subagent | core | 23 | 子 agent 后端都要驱动 Agent 与 Session |
client | api | 19 | UI 通过生成的 RPC 契约与 host 对话 |
session | core | 16 | 持久化与投影都建立在事件日志之上 |
extensions | client | 13 | 自我修改子系统有浏览器半边 |
workflow | core | 12 | 编排要 parent 出子 agent |
interaction | core | 11 | 批准与提问挂在 agent 生命周期上 |
谁被依赖得最多
排除掉那个人人依赖的 invariants 之后,扇入排名基本就是这个项目的"重心图"。
前四名 session / llm / agent / tools 就是核心骨架本身——
这四个包的接口一变,全仓库跟着动。
被多少个包依赖(扇入 top 15)
统计口径:内部 dependencies + peerDependencies;已排除扇入 218 的 dsh-invariants
各包组的包数量(top 12,共 49 组 / 219 包)
client 一组占了 39 个包——整个浏览器 UI 也是按"一个功能一个插件"拆的
想加东西时,先看你要挂的扩展点落在哪一层:落在能力族及以上(绝大多数情况),写插件就行,不会牵动别人;
一旦要改核心骨架那四个包的接口,扇入 47–84 意味着影响面是全仓库级的,仓库规约也要求同时更新 docs/architecture.md。
04功能盘点
1. 内核骨架 packages/core/
| 包 | 职责 | ctx key |
|---|---|---|
session | append-only 的 SessionEvent 日志与内存存储 | ctx.sessions |
system-prompt | prompt 分节与 tool schema 组装 | ctx.systemPrompt |
tools | 分层作用域的工具注册表 + 受保护的执行管线 | ctx.tools |
agent | Agent 接口、活跃注册表、agent/* 事件 | ctx.agents |
agent-loop | 实现该接口的默认驱动(唯一的具体循环) | ctx.agentLoop |
scope | 按 agent 隔离注册的原语 | 库,无 key |
2. 模型接入 packages/llm/
llm-deepseek— DeepSeek 直连适配器llm-pi-ai— 多厂商适配:Anthropic、OpenAI、Bedrock、Vertex、Azure、Codex,以及任意 OpenAI 兼容的自建网关。手写的模型默认按纯文本处理,需要视觉能力时在settings.yaml里给该模型加input: [text, image]llm-retry(provider 级重试策略)、token-meter(回放感知的 token 计量)
3. 模型可见的工具集
docs/tool-catalog.md 是自动生成的完整 JSON Schema 目录(1873 行)。生成器会真的启动每个工具插件再读
ctx.tools.schemas(),因为 schema 不是静态可知的;同时有一道 glob 门禁,新增工具包却漏进目录会直接失败。
| 域 | 工具 |
|---|---|
| 文件 | read write edit read_image、str_replace_editor、glob grep(内置 @vscode/ripgrep,不依赖宿主 rg,也不经过 shell 层) |
| 执行 | bash(一次性,带沙箱)、pwsh(Windows 分支)、持久 PTY 的 bash 与 terminal_open/read/send/signal/list/close |
| 后台作业 | job_list job_output job_kill——后台 bash、PTY send、subagent 共用同一套管控 |
| 代码智能 | lsp(provider 无关;没有注册 provider 时返回结构化的 LSP_UNAVAILABLE 而不是改变 schema) |
| 网络 | web_search web_fetch |
| 任务组织 | todo_write、exit_plan_mode(计划模式)、create_goal update_goal get_goal、schedule_create/list/delete(会话内定时提醒) |
| 委派 | subagent subagent_fork、send_message interrupt_agent list_agents、子 agent 侧的 report |
| 编排 | workflow(模型自己写编排脚本,在 worker 线程里执行)、ralph(固定策略:每轮开一个全新子 agent 迭代) |
| 会话检索 | session_search session_trace session_event_read session_event_search session_event_trace——SQLite 全文检索,独立于上下文压缩 |
| 技能 | skill(技能目录 + 载入器) |
| 人机 | ask_user_question——挂起该次工具调用,直到真人回答 |
| 自举 | cordis_define cordis_run cordis_inspect_* cordis_undefine——agent 修改自己正在运行的插件树。默认不在任何发行树里,必须显式 opt-in |
| Code Mode | run_code——把所有工具生成成一份 SDK,让模型写程序批量调用,而不是一次次 tool call。DSH_TOOLS_MODE=native|code|both |
4. 安全与执行环境
sandbox 接缝 + 本地后端(Linux bwrap / Landlock、macOS Seatbelt、Windows ACL 受限令牌),
sandbox-policy 解析按会话持久化的策略,fs-sandbox 在 ctx.fs 层拦截写入,
permission-presets(新会话默认 workspace-write)、user-approval(人工批准接缝)。
仓库还自带 native/landlock-run 这个自研 Node 原生插件。
新会话默认 workspace-write:Bash 与文件系统的写限制在会话工作区加平台临时目录;
读、网络访问、进程可见性并不受限。遥测默认关闭,且发行版没有内置脱敏规则——显式开启后导出内容可能包含消息正文、工具参数与工作区路径。
5. 会话数据面
JSONL / SQLite 双持久化后端、投影接缝、由日志生成的会话标题、fork / resume、
compaction(上下文压缩)、spill(超长工具结果外溢到存储)、
attachment(内容寻址附件)、session-telemetry(OTLP,默认关闭)。
6. 上下文注入 packages/context/
重点是 agent-instructions:兼容 AGENTS.md / CLAUDE.md,从
$DSH_HOME/AGENTS.md 一路读到当前工作目录;并且在 read/write/edit
成功之后动态发现新进入目录的嵌套指令文件,报告变更与删除(渲染预算 65,536 字节)。
同目录下内容完全相同的 CLAUDE.md 与 AGENTS.md 会折叠成一份,不会重复计费。
另有 time-context、tmux-context、session-reference。
7. 互操作层
这一层是它和外部生态结合的关键,也是同类项目里比较少见的完整度。
- MCP 客户端:连接任意 MCP server,工具以
mcp__<server>__<tool>注册;支持 stdio / streamable-http、断线指数退避重连、HMR 热插拔 - ACP server:把 harness agent 暴露给 Agent Client Protocol 客户端(编辑器类集成)
- SDK:JSON-RPC 协议 + TypeScript 客户端 + server 插件;Python SDK 自带 Node 运行时,宿主无需安装 Node
- Hook 桥:直接运行你已有的 Claude Code
hooks.json与 Codex hook 配置,映射到 harness 的类型化拦截点 - 子 agent 后端可以是别人家的 agent:
subagent-claude-code(走官方 Claude Agent SDK)、subagent-codex、subagent-acp、subagent-dsh-sdk
8. 产品外壳
apps/cli—dsh命令:profile 启动 /web/headless/plugin包管理 /--dump-configapps/web+packages/host/+packages/client/— 完整 Web UI,约 30 个ui-*浏览器端插件(会话、权限、模型设置、计划、待办、技能、子 agent、工作流、附件、主题……)packages/preset/— 按会话组合 agent;apps/cli/config/agent-presets/下自带minimal/standard/code/cordis四套
9. 工程设施
typert(类型图生成器 + 运行时注册表)、guard(循环卫生 + 工具超时)、test-support,
以及 scripts/ 下大量文档与目录生成器和门禁。仓库自身的工程强度很高:逐文件 100% 覆盖率门禁、
无密钥的 snapshot 回放测试、双语文档同步、包级运行时不变量。
05可重建性:日志即真相
大多数 agent 框架里,日志是旁路产物——尽力而为地记一点,事后想复盘"模型当时到底看到了什么",往往拼不回来。 dsh 把这件事反过来:日志是唯一真相,模型历史是从日志推导出来的,从不单独存储。 这条规约在仓库里叫 model-visible ⟺ logged,而且——这是关键——它不是文档里的承诺,是每次请求都实际执行的断言。
本章说的事情有一个可交互的实证:用仓库里真实录制并提交的会话日志逐事件回放五个场景, 左边是渲染出来的对话,右边是它对应的日志原文。无需 API key,不执行任何代码——因为日志本身就够了。
日志里存了什么
SessionEventMap 是 append-only 的事件词汇表,序号连续、无损 JSON,所以持久化后端可以逐字存原始日志。
其中有一对容易被忽略的区分:
| 事件 | 是什么 | 谁消费 |
|---|---|---|
assistant/chunk | 原始流式分块,token 级保真 | UI 回放、replay 测试。不参与模型历史推导 |
assistant/message | 该 step 组装完成的助手消息 | 模型历史推导用的就是它 |
两者同时在日志里,服务不同消费者:要逐字复现当时屏幕上的流式输出,读 chunk; 要重建下一次请求的 messages,读组装后的消息。这也是"回放 UI"和"重建请求"能各自精确的原因。
重建的两条折叠路径
路径 A:消息历史
deriveMessages() 沿"surface 节点"折叠一个纯投影函数 deriveEventMessage。
只有三类事件会投影成消息:
user/message— 逐字通过。人类直接输入、agent.inject()注入的上下文、目标续跑轮次,三者都原样投影,靠source字段区分assistant/message— 逐字通过,但空内容的会被跳过:那种事件只是用来承载 max-tokens step 的 usage,不能往对话里塞一条没内容的助手轮次tool/result— 逐字通过
投影层刻意不加任何框架。像 <system-reminder> 这种包装由生产者在写入 content 时就烤进去
(agent-instructions 就是这么做的),投影只做 verbatim pass-through。
这条约束的意义是:日志里长什么样,模型看到的就是什么样,中间不存在第二次加工。
上下文压缩也在同一套机制里:compaction 写入的是一个 replace 型 surface 节点,它遮蔽掉被替换的历史范围,
于是推导自动排除被压掉的部分——压缩不是删日志,是在日志上加一层遮蔽,原始事件仍在,人类转写仍能读到完整对话。
路径 B:请求头
request/header 记录 model、system、temperature、maxTokens、stop、tools 这些"这次请求在什么配置下发出"的事实。
它只在变化时写(用 headerEquals 判等,避免重复记账),
foldRequestHeader() 扫一遍日志取最后一个快照,就是当时生效的头。
断言长什么样
agent-loop 的不变量伴生包在每一次 llm/stream 上挂了一个检查
(global + prepend——前置是为了防止某个会短路的 replay 监听器把检查静默掉)。
对每个由主循环构造的请求,它逐项验证:
| 检查 | 不通过意味着 |
|---|---|
请求对象与 messages 数组均已冻结 | 有人能在发出后改写它,日志就不再是真相 |
带 sessionId 且能解析到活的 session | 这次请求无从归属,谈不上重建 |
日志里存在 step/start | 请求发生在任何被记录的 step 之外 |
能折叠出 request/header | 配置事实没有被记录 |
JSON.stringify(实际 messages) === JSON.stringify(deriveMessages()) | 发出去的和日志能重建的不是同一份东西 |
| 头字段逐项相等:model / system / temperature / maxTokens / stop / tools | 请求配置与记录的配置发散 |
第五项是全部的核心:不是"结构相似",是 JSON 序列化后逐字节相同。 任何让实际请求偏离日志推导的改动——某个插件偷偷往 messages 里塞了一条、某处绕过事件直接改了历史—— 都会在那一次请求上当场失败,并被指名为 log-reconstruction desync。
"我们会记录日志"是所有框架都会说的话。 "我们在每次请求上验证日志足以重建这次请求"是一个可以被证伪的工程主张—— 它把审计从"事后希望日志够用"变成"发散的那一刻就跑不下去"。这两者的差别,在需要对外解释 agent 行为时是决定性的。
读取端:宁可失败,也不给你一份看起来对的错误重建
KNOWN_SESSION_EVENT_TYPES 是生成的常量,列出当前这个 build 认识的全部事件类型。
读日志时遇到不认识的类型,持久化读取路径拒绝解释整份日志——除非该事件自带 ignorable: true 标记。
理由写在源码注释里:这样的日志多半是更新版本的 harness 写的, 而静默跳过一个必需事件会重建出一个错误的会话。所以新增事件类型时,作者必须显式决定 "旧版本读到它可以跳过吗",而不是让旧版本自己猜。
崩溃恢复:不截断,而是留下一个不可能自然产生的标记
重载一份崩在半路的日志时,会看到有 turn/start 却没有 turn/end。后端不截断——
长任务的单个 turn 可能非常大,而那些事件在崩溃前已经安全落盘了。它补一个合成的
turn/end { reason: { kind: 'interrupted' } } 把 turn 配平。
妙处在于 interrupted 是唯一一个主循环永远不会发出的 TurnEndReason
(取消走的是 aborted)。用一个不可能自然产生的值来标记合成事件,
读日志的人就永远不会把"崩溃恢复的痕迹"误认成"正常结束"。这是个很小的设计决定,但它保住了日志的诚实性。
这对你意味着什么,以及它的边界
| 场景 | 能拿到什么 |
|---|---|
| 合规 / 审计 | 能回答"当时模型看到了什么",且这个回答由每次请求的断言背书,而不是靠日志写得够全的运气 |
| 事故复盘 | agent 做了蠢事时,可以精确重建它当时的完整输入,包括注入的上下文与工具结果 |
| 评测 / 回归 | fork、resume 与无密钥 snapshot 回放都建立在同一条事件流上,评测集就是真实会话 |
它保证的是输入可重建,不是这两件事:
- 不保证输出可复现。重建的是发给模型的请求,模型本身的采样随机性不在此列
- 不是防篡改。JSONL 后端默认写带校验和的 Zstandard 帧,那是防损坏(截断、坏块),不是防抵赖——日志没有签名也没有哈希链,能改文件的人就能重写日志并重算校验和。要不可抵赖,得自己在外面套一层
06能够怎么用
A. 当成通用编码 agent(最省事)
npx @deepseek-ai/dsh web # Web UI,默认 http://127.0.0.1:3080
Settings → Models 填 DeepSeek API key(存到 $DSH_HOME/.credentials.yaml,只写不读回,
页面拿到的是脱敏描述符)→ 选工作区 → 开会话。模型改动下一次请求即生效,不用重启。
从源码运行
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness && pnpm install && pnpm run build
pnpm dsh web
B. 一次性任务 / 无人值守
export DEEPSEEK_API_KEY=sk-...
dsh --profile headless "fix the failing test in this workspace"
创建一个持久化会话、跑完、把最后一条 assistant 文本打到 stdout,completed 退出 0,否则退出 1。
不起任何监听端口,成功时不写 stderr。
C. 程序化嵌入
Python
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(provider="deepseek-official", model="deepseek-v4-flash",
cwd="/abs/workspace", session_root="/abs/sessions",
cordis="examples/jsonrpc-agent/minimal.cordis.yml") as h:
r = h.run("Inspect the repository and fix the failing tests.", session_id="job-001")
print(r.final_response)
同一个 session_id 复用会保留会话独占的 bash 进程——工作目录、导出的环境变量、shell 函数都还在。
独立任务用新 id。TypeScript 走 SDK client,编辑器类客户端走 ACP。
D. 改配置定制(不写代码)
所有定制都是往 cordis.yml / cordis.patch.yml 叠一层。例如接一个 MCP server:
- id: mcp-github
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: github
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-github']
env:
GITHUB_TOKEN: !!js process.env.GITHUB_TOKEN
dsh web --patch ./my.cordis.yml
仓库里已有现成 overlay 可以照抄:
examples/web-schedule— 持久化的会话内定时提醒examples/mcp-memory— 通过通用 MCP 客户端接第三方记忆服务examples/headless-agent/e2b.cordis.yml— 把整个执行世界搬到 E2B 远程沙箱examples/acp-agent、examples/jsonrpc-agent、examples/web-cordis
E. 写插件并分发
一个 npm 包,package.json 里声明 bundle 清单,然后装进 profile:
{
"name": "dsh-hello-plugin",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
dsh plugin --profile myprofile add ./my-plugin # 或 github:owner/repo
dsh --profile myprofile
官方建议给插件仓库打 dsh-plugin topic 便于被发现。
07和你的 GitHub 项目怎么结合
按投入从低到高分五层。前两层今天就能用,不改一行业务代码。
层次 1零代码:让 agent 读懂你的项目
在你的项目根目录放这些文件即可,性价比最高:
AGENTS.md(或CLAUDE.md)——构建命令、目录约定、代码风格、禁区。会被自动加载,并且在 agent 读写到子目录时动态加载packages/xxx/AGENTS.md这类嵌套指令.agents/skills/<name>/SKILL.md或.dsh/skills/——把重复流程固化成技能(发布流程、review checklist、迁移脚本)。项目根按最近的.git祖先判定,文件改动有 watcher 实时生效- 已有 Claude Code 的
.claude/hooks.json?挂dsh-hooks-claude-code桥直接复用,不用重写
技能发现的优先级(同名时序号小的层赢):
| rank | 来源 | 路径 |
|---|---|---|
| 100 | project-dsh | <projectRoot>/.dsh/skills |
| 200 | project-agents | <projectRoot>/.agents/skills |
| 300 | custom | 配置里的 customSkillDirs |
| 400 | user-dsh | $DSH_HOME/skills |
| 500 | user-agents | ~/.agents/skills |
本仓库自己就是这么做的:根 AGENTS.md + .agents/skills/ 下十来个技能 + .agents/notes/ 决策记录。
层次 2CI:在 GitHub Actions 里跑 headless
没有官方 Action,需要自己写一步:
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npx -y @deepseek-ai/dsh@0.1.0-rc.5 --profile headless "${{ github.event.inputs.task }}"
env:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
DSH_PERMISSION_MODE: workspace-write
适合:自动修 flaky 测试、按 issue 生成补丁、批量重构、文档同步检查。两个注意点:
- Linux 沙箱走 bwrap / Landlock,容器化 runner 里可能需要放宽策略或换
danger-full-access(只在一次性 runner 里用) - 首次
npx会拉不小的依赖闭包,建议缓存或预装
注:上面这段 workflow 是按 CLI 文档写的,我没有在真实 Actions runner 上实测过。
层次 3把你项目的能力接进 agent
- 已有 MCP server → 一段
mcp-client配置就接上了,工具自动出现在模型面前 - 没有 → 写个 tool 插件,
docs/cookbook/adding-a-tool.md有完整步骤。注意本仓库要求工具在设计阶段就定好 UI 渲染意图(generic/terminal/diff)
判断标准:你项目的 API、内部系统适合走 MCP;只有需要访问 session 日志、agent 生命周期、权限决策这类深度能力时,才值得写原生插件。
层次 4把 agent 嵌进你的产品
- 后端是 Python → Python SDK。
session_id即会话身份,JSONL 日志落到你指定的目录,可直接做审计 - 后端是 Node / TS → SDK client,或者直接 mount 一棵 cordis 树
- 做 IDE / 编辑器插件 → ACP server
- 做多 agent 编排 →
subagent家族,甚至可以让 dsh 调度 Claude Code / Codex 作为子 agent
层次 5Fork 定制(注意:回流不走 PR)
要长期定制,建议不要改 packages/,而是新建自己的 bundle 包叠一个 patch 层——
这正是它架构设计的用意,也让上游同步(git pull upstream)几乎无冲突。
真要改核心,仓库的门禁很严:pnpm run test:coverage(逐文件 100%)、test:snapshot、
doc-sync、hygiene,而且非平凡改动必须在同一个 PR 里附一份 Agent Note。
CONTRIBUTING.md 写得很直接:"我们目前无法接受外部 PR"。
公开仓库 deepseek-ai/deepseek-harness 的 PR 数确实是 0,而它有一万两千多个提交——
开发在私有仓库进行,公开仓库是发布镜像。
官方指定的参与路径是另外三条:在 Discussions 报问题并投票、做插件并打 dsh-plugin topic、写文章和教程。
官方原话:"我们并不认为官方仓库中的包天然就比社区开发的包更重要……可以将本仓库看作一种理念、一份官方示例以及一处灵感来源,
而不是我们要求社区遵循的方向。"所以定制的正道是在旁边造,而不是往里改。
08拿它能做什么
前面几章讲的是"它是什么、怎么接"。这一章讲接完之后能换来什么,以及按什么顺序去换。 排序的依据是单位投入产出:越靠前的档位见效越快、退出成本越低。
| 档位 | 换来什么 | 投入 | 什么信号说明该进下一档 |
|---|---|---|---|
| ① 当编码 agent 用 | 一个能读能改能跑命令的助手 | 10 分钟 | 它开始在你的项目上重复犯同一类错 |
| ② 让它读懂项目 | 错误率下降,不用每次重复交代 | 半天,持续演进 | 有流程你已经手工重复了三遍以上 |
| ③ headless 进 CI | 无人值守的重复劳动 | 1–2 天 | 自动化跑出的 PR 有一半以上能直接用 |
| ④ SDK 嵌入产品 | agent 成为你产品的一个能力 | 1–2 周 | 你需要的能力现有工具集里没有 |
| ⑤ 写插件深度定制 | 自己的垂直 agent / 执行世界 | 数周起 | —(终点,别急着到这里) |
档位 ①当编码 agent 直接用
做什么:日常编码、快速读懂陌生代码库、批量改、补测试、查线上问题。
怎么做:npx @deepseek-ai/dsh web,配模型,选工作区,开干。
第一天最该做的一件事:拿你最熟的那个项目,问它三个你已经知道答案的真问题。 你要观察的不是它答得对不对,而是它在哪里绕路、查了哪些不该查的文件、误解了什么约定—— 这些卡点就是档位 ② 的输入清单。
相对闭源产品最实在的一条差异:模型可以指向任意 OpenAI 兼容端点,包括你自己内网的推理服务。
llm-pi-ai 支持自定义 provider(base URL + 协议 + 凭据 + 模型列表),
意味着代码与上下文可以完全不出内网。对不能把代码交给外部 API 的团队,这一条本身就足以决定选型。
档位 ②让它读懂你的项目
做什么:把"你们团队的常识"变成 agent 的常识。
怎么做:从它犯的错倒推,而不是一开始就写一份大而全的规范。
- 它跑错构建命令 →
AGENTS.md写死构建与测试命令 - 它改了不该碰的目录 → 写明禁区与所有权
- 它重复问同一个上下文 → 写进项目约定
- 某个流程你已手工重复三遍 → 固化成
.agents/skills/<name>/SKILL.md
嵌套的 AGENTS.md 值得专门用起来:agent 读写到某个子目录时才加载该目录的指令,
所以大仓库可以做到"进哪个模块看哪份规矩",而不是把所有规则堆在根文件里烧 token。
档位 ③headless 进 CI,把重复劳动交出去
做什么(按落地难度排序):
- 依赖升级后的编译/测试修复
- 按 issue 描述生成初版补丁,交人 review
- 大批量机械改造:API 迁移、日志规范化、类型补全
- 文档与代码的同步检查
- flaky 测试的定位与修复
怎么做——三条能少踩坑的经验:
- 先用
workflow_dispatch手动触发跑一两周,攒成功率数据,再谈自动触发 - 只让它出 PR,不给直接推分支的权限。人 review 是这个阶段的安全网,不是负担
- 权限模式与沙箱显式声明。容器化 runner 里 bwrap/Landlock 可能受限,宁可换一次性 runner 也别默默放开策略
长任务可以用 ralph:每轮开一个全新子 agent 继续同一个目标,避免单个上下文越跑越脏。
档位 ④把 agent 嵌进你的产品
做什么:agent 不再是你用的工具,而是你产品里的一个能力——自动化运维、数据分析、工单处理、报告生成。
怎么做:Python SDK 起步最快(自带运行时,宿主不用装 Node)。三个要点:
session_id就是会话身份。复用同一个 id 会保留持久 bash 进程(cwd、环境变量、shell 函数),适合多轮长任务;独立任务务必换新 id- JSONL 会话日志直接就是审计记录——每次模型请求与工具调用都能重建
cordis参数指定组合文件,可以只给这个场景需要的工具,而不是把全套塞给模型
这一档的隐藏收益:自己从零造 agent 后端时,真正耗时的从来不是主循环, 而是沙箱、权限、会话持久化、上下文压缩、流式协议、审计日志、中断恢复——这些它都已经有了。
档位 ⑤写插件,做自己的垂直 agent
做什么:接自己的内部系统、造领域专用 agent、把执行世界换到远程沙箱或容器。
怎么做——按这个顺序,能省掉大量返工:
- 先用 patch 覆盖:能靠
--patch改配置解决的,不要写代码 - 再用 preset 组合:一个 agent preset 就能定义"这个场景用哪些工具 + 什么 prompt",这是垂直化最便宜的一步
- 能力接进来优先走 MCP:已有 MCP server 的话是一段配置的事
- 最后才写原生插件:只有需要 session 日志、agent 生命周期、权限决策这类深度能力时才值得
唯一的红线:不要改 packages/。新建自己的 bundle 包叠一层 patch,
上游同步几乎零冲突;改了核心,每次 git pull upstream 都是一场手术。
再往外想一层
上面五档是"用它"。下面几条是"用它做别人还没做的事"——都建立在这次分析里确认过的能力上。
| 方向 | 为什么它适合做这个 |
|---|---|
| 跨厂商 agent 调度中枢 | 子 agent 后端可以是 Claude Code(官方 SDK)、Codex、ACP 客户端或另一个 dsh。也就是说可以让不同厂商的 agent 各干擅长的事,而调度、权限、会话日志统一在你这一层。这个位置目前几乎没人占。 |
| 垂直 agent 产品的底座 | 换 system prompt + 换工具集 + 一个 preset ≈ 一个新产品。工具注册表与 prompt 组装都是插件,垂直化的边际成本很低,而底下的沙箱、持久化、压缩、审计是现成的。 |
| 可审计 / 合规场景 | "model-visible ⟺ logged"这条不变量的商业价值被低估了:凡是进过模型的东西都能从日志重建,还有运行时断言兜底。金融、医疗、政企这类要求"说得清 agent 当时看到了什么"的场景,这是硬通货。机制与边界见「可重建性」一章。 |
| agent 行为研究 / 评测平台 | append-only 日志 + fork/resume + 无密钥 snapshot 回放,本身就是一套评测底座。想研究"Code Mode 相对逐次 tool call 省多少 token"这类问题,它是少见的能直接开跑的开源生产级 harness。 |
| 找生态缺口 | 由于官方不收外部 PR,插件生态是唯一的贡献路径,因此它起步就不冷清:dsh-plugin topic 下已有 419 个公开仓库(TypeScript 238 / JavaScript 112 / Python 25)。但分布很不均——头部集中在消费向(角色扮演、内容发现、技能生成),工程向的垂直工具集仍然稀疏。缺口在后者,不在"没人做"。 |
生态现状
因为官方不接受外部 PR,社区能量全部涌向了插件与技能,生态起步就不冷清。 完整的归类、数据与代表项目见下一章「生态地图」。
下一步:一条具体路径
如果这个项目让你有点心动但不知道从哪下手,按这个节奏走,每一步都有可验证的产出:
npx @deepseek-ai/dsh web 跑起来,拿最熟的项目问三个你已知答案的真问题。
记录它绕路的地方。产出:一份卡点清单。
按卡点清单写 AGENTS.md,挑一个你手工重复过三遍的流程写成 skill。
顺手跑一次 dsh --profile web --dump-config,看清自己这台机器实际装了什么。
产出:同一批问题重问一遍,绕路明显减少。
选一个真实痛点做 headless,手动触发,只出 PR。跑两周,记录"能直接用的 PR 占比"。 产出:一个数字,用来决定要不要继续投。
占比够高 → 扩自动化范围,或往档位 ④ 走,把它变成产品能力。 占比不高 → 回到档位 ②,补规则和技能,而不是急着写插件。
什么时候该进下一档?当上一档已经稳定产出价值,而且你能具体说出"它现在卡在哪"的时候。 反过来,如果档位 ① 都还没跑顺就去写插件,你只是在给自己造第二个需要调试的系统—— 而这个系统的问题会和第一个的问题纠缠在一起。
09生态地图
官方在 CONTRIBUTING.md 里把话说死了:"我们目前无法接受外部 PR"。
公开仓库 deepseek-ai/deepseek-harness 的 PR 数确实是 0,而它有 12,293 个提交——
开发在私有组织的仓库里进行,公开仓库是发布镜像。
于是社区的全部能量只有一个出口:做插件。这让 dsh 的生态呈现出一种少见的形状—— 它不是围绕"给上游修 bug"长出来的,而是围绕"在旁边造自己的东西"长出来的。官方对此的表述也很明确:
我们并不认为官方仓库中的包天然就比社区开发的包更重要……可以将本仓库看作一种理念、一份官方示例以及一处灵感来源, 而不是我们要求社区遵循的方向。
规模与语言分布
dsh-plugin topic 下的公开仓库数。这个数字在我做这次调查的几十分钟内从 419 涨到了 425——
正好是公测发布当天,可以把它理解为一张快照而不是稳定值。
主语言分布
419 个仓库的快照。TypeScript 的绝对优势来自 Web UI 与 host 插件都是 TS
按方向归类
下面这张分布图取的是按 star 降序的前 40 个仓库(去掉官方仓库本身后 39 个), 约占总量的 9%。长尾未抽样,所以它反映的是"生态的头部长什么样",不是全量普查。
头部 39 个项目的方向分布
分类为本文所做,依据各仓库的描述文本
七个方向,各自在做什么
12 个UI / 工作台 / 皮肤 — 最卷的方向
dsh-web-ui(398★,任务板、git graph、右侧面板、移动端远程 UI、宠物、实时 token 计量)、
DSH-better-sidebar(92★,带文件编辑与终端的侧栏工作台)、
dsh-cc-tui(148★,Claude Code 风格全屏终端)、
oh-dsh-desktop(55★,macOS 工作台)、
还有 @file 提及、桌面通知、VS Code 跳转、生成式 UI 卡片、鲸鱼皮肤,
甚至有人做了 dsh-ads(86★)——2005 年中文站风格的假广告,连"关闭叉热区比视觉小得多"都还原了。
这说明 Web UI 的插槽机制确实好用。三十多个 ui-* 插件构成的前端,
让第三方改外观和加面板的门槛低到可以拿来开玩笑。
6 个补能力缺口 — 生态最有价值的部分
视觉是最大的洞,至少四个项目在补:
modlens(667★)、
agent-vision-toolkit(487★)、dsh-vision-toolkit(129★),
以及绕开视觉的另一条路——dsh-browser(23★,Chrome 侧栏让 DSH 直接操作浏览器,不需要视觉能力)、
browser-bridge(21★,Rust)。此外 argo(55★)专门做面向 agent 的多语言搜索。
modlens 是全生态最能说明架构价值的一个例子:模型不支持视觉是模型的问题,
但因为 LLM 适配器是可注册的插件,第三方在不碰一行官方代码的前提下把洞补上了,
还让补丁以「DeepSeek-V4-Flash (modlens vision)」这样一个新模型选项的形式出现在 UI 里。
这比任何架构文档都更能说明"一切皆插件"落地之后是什么样。
6 个独立产品 — 把它当底座,而不是当工具
OpenBiliClaw(1.9k★,跨平台内容发现 agent)、
deeptide(1k★,Swift-native macOS 编码 agent)、
mobius(908★,自演化 Agent OS)、
Abu-Cowork(279★,Claude Cowork 的开源替代)、
phi(70★,Go,带权限门的编码 agent)。
这一类印证了前一章档位 ④ 的判断:自建 agent 后端真正耗时的不是主循环,所以有人直接拿现成的往上造。
5 个目录 / 索引 — 元生态已经过剩
awesome-dsh-plugins(241★,带每日兼容性追踪)、
awesome-deepseek-harness(108★)、awesome-dsh-plugin(54★)、
awesome-DSH-plugin(30★)、dsh-find-plugins(20★)。
五个 awesome 列表,比不少被它们收录的插件还多。
真要做索引的话,"每日兼容性追踪"那个思路是唯一有增量价值的——因为 pre-release 阶段插件失效很快。
3 + 3 + 3编排增强 / 技能人格 / 基础设施
- 编排增强:
dsh-agent-teams(51★)、dsh_workflow(33★,把一次性多 agent 调度升级成带持久化、治理、可观测与恢复的 workflow 层)、mstar-harness(39★) - 技能 / 人格:
dot-skill(21k★,把人的思维蒸馏成 AI 人格,同时兼容 Claude Code / Codex / OpenClaw / dsh,装进~/.dsh/skills/即可)、ex-skill(1k★)、MuseAI(507★) - 基础设施 / 安全:
axern(245★,Go,agent 沙箱与不可信代码执行)、open-managed-agents(228★)、openguardrails(24★,agent 安全协议与中立基准)
从这张地图能读出什么
| 观察 | 含义 |
|---|---|
| 技能层是跨产品可移植的 | 生态里最大的项目 dot-skill 一份代码同时服务 Claude Code、Codex、OpenClaw 和 dsh。技能只是带约定的 Markdown,不绑定宿主——所以写技能的投入不会被单一产品套牢,这也是前面把"写技能"排在低档位的原因 |
| UI 卷,工程向空 | 皮肤、侧栏、TUI、通知有十几个,但做 CI 集成、代码质量、领域专用工具集的几乎没有。缺口在工程向,不在"没人做" |
| 补能力缺口 > 换皮 | 星数最高的插件类项目(modlens 667、vision-toolkit 487)都在补模型或框架的真实短板,而不是改外观。这是判断一个插件值不值得做的现成标尺 |
| 不收 PR 反而催生了生态 | 如果上游收 PR,这 425 个仓库里相当一部分会变成官方仓库里的 PR。把贡献者挡在门外,等于强制他们成为独立作者——这是个值得琢磨的开源治理选择,代价是官方仓库的演进完全不透明 |
全部数据取自公开网页(GitHub 仓库页、dsh-plugin topic 检索结果按 star 降序前 4 页)而非 API——
上游仓库不在本次分析环境的 API 授权范围内。总数 425、语言分布取自 419 时的快照,
归类为本文依据仓库描述所做,非官方分类。公测首日的数据变化很快,请把它当快照看。
10取舍与现状
和 Claude Code 最大的差异是:Claude Code 是封闭产品加扩展点,而 dsh 连主循环都是可替换的插件行。 代价是概念负担明显更重——profile / bundle / patch / capability seam 这四层概念要先理解,才能有效改它。
仓库自我定位是 developer preview,明确写着"会有破坏兼容性的变更"。
dsh-session 的 SESSION_FORMAT_VERSION 停在 0 且不做兼容承诺,
后端直接拒绝旧的磁盘格式。做集成时按"接口会变"来规划,别把 on-disk 格式当契约。
什么时候不该选它
- 只想要一个开箱即用的编码助手:成熟商业产品更省心,插件化带来的自由度你用不上,概念负担却要照付
- 需要长期稳定的 API 或磁盘格式:等第一个正式 tag 再说,现在的 pre-release 阶段明确允许破坏性变更
- 团队里没人愿意读 Cordis:不理解插件模型就只能把它当黑盒用,那等于付了定制化的成本却拿不到定制化的收益
11分析方法
本页结论来自对仓库源码与文档的静态阅读:docs/architecture.md、各 package 组的 README、
生成的 docs/tool-catalog.md、apps/cli/reference/README.md、
docs/user/ 下的用户指南,以及 packages/ 下的具体实现与配置清单。
没有构建或运行过这套系统(分析环境没有 API key),所以运行时行为、性能与实际模型效果不在本文的证据范围内;
涉及推测的地方(例如 GitHub Actions 那段 workflow)已在正文中标注。包数量与代码行数由
find 与 wc -l 统计得出。
图表的数据来源
「包依赖结构」一章的所有数字都是脚本现算的,不是抄文档:遍历 packages/<group>/<pkg>/package.json,
取 dependencies 与 peerDependencies 中以 @deepseek-ai/dsh- 开头的条目,
据此统计扇入、按组聚合跨组边权、统计各组包数。仓库自带的 docs/module-graph.md 只用
peerDependencies(它声明那是运行时依赖的规范信号),口径比这里窄,所以两边的绝对数会有出入;
本文取并集是为了反映"实际会被装进去的东西"。
分层图里的四层是我按依赖方向做的聚合判断,不是仓库里既有的分类——仓库官方的分组是
packages/README.md 里那 49 个组。配色经过色觉安全校验(对比度、色盲可分辨度),
且所有色块都带可见文字标签,不依赖颜色单独传达信息。