AGENT HARNESS · 深度求索 / 一切皆插件

DeepSeek Harness

一句话说清:把「大模型」变成「真能干活」的那层工程外壳,就是 Harness。 深度求索开源的 dsh 把这层外壳做到了极致—— 没有特权内核,连智能体循环本身都是插件,任何一行都能被配置替换。 这里从 npx @deepseek-ai/dsh web 开始, 带你走完安装、插件、技能、沙箱、审批与工程集成的完整链路。

9大板块 60+命令与配置 6内置 profile 插件可替换
dsh · ~/projects/my-app workspace-write · ask

dsh web --port 3080

plan 拆解任务:读取目录结构 → 定位 OrderService → 编写接口 → 补测试

tool read("src/main/java/.../OrderService.java")

tool edit(3 files, +96 −12) ← 先读后写策略已满足

tool bash("mvn -q test -Dtest=OrderServiceIdempotentTest")

ask 该命令要写入工作区外目录,是否放行?(沙箱模式 read-only 触发审批)

done 6 passed · 改动停留在工作区,不替你推远端

注意倒数第二行:沙箱管「文件效果」,审批管「是否放行」,两者独立配置。 最后一行则说明:Harness 把「动手」做到底,把「拍板」留给你。

01 / WHAT IS HARNESS

Harness 到底是什么

先建立心智模型,再谈命令,才不会用成「高级聊天框」

模型 = 大脑

负责理解意图、拆解任务、写代码、做判断。它聪明,但只能「说」,摸不到你的文件系统。

Harness = 手脚与神经

读写文件、执行命令、调用工具、回传结果、控制上下文预算与终止条件——把「说」接成「做」。

Skills = 肌肉记忆

把「怎么写架构、怎么审 SQL、怎么查依赖」这类固定套路固化下来,让大脑不必每次重新发明流程。

维度纯对话式 AIHarness 形态的 Agent
输入 你贴过去的片段 整个仓库:目录树、依赖、配置、提交历史
输出 一段需要你复制粘贴的文本 直接落到文件系统的 diff,可跑、可测、可回滚
闭环 贴 → 试 → 报错 → 再贴(人工当回路) 改 → 跑测试 → 看报错 → 自己再改(机器当回路)
稳定性 依赖你的提问水平 依赖你给的技能、约束与验证手段
最合适的事 答疑、解释、给思路 跨文件重构、批量迁移、补测试、修 CI、写文档
先设预期:Harness 不是「一键做完整个需求」。

它的甜区是边界清晰、有验证手段的任务。任务越模糊、越依赖业务上下文,你越该先把它拆小、把验收标准写清楚,再交给它。

02 / WHY THIS ONE

为什么值得单独学这一套

它不是又一个「套壳 CLI」,架构选择直接决定你能改到什么程度

主张 01

一切皆插件,连智能体循环都是

模型适配器、工具注册表、会话日志、沙箱、审批策略、甚至连 agent-loop 本身 都由插件提供。不存在「需要打补丁的特权内核」—— 扩展 dsh 的方式是把插件挂在其他插件旁边。

底座是插件框架 Cordis:插件向共享上下文贡献服务、类型化事件与可逆副作用。

主张 02

注册即副作用,卸载即回滚

提示词片段、工具 schema、适配器、事件监听,全部通过 ctx.effect()ctx.on() 安装。插件卸载时,这些注册按注册顺序自动撤销, 你不需要手写 removeListener

热重载因此是安全的:换掉一个提供方,不需要重启进程。

主张 03

能力是「seam」,不是硬编码

一项能力被拆成三个角色:Service Definition(声明接口)、 Service Provider(实现)、Consumer(面向模型的工具)。 换掉 Provider,Definition 与工具都不用改。

把文件系统指向远程沙箱,Bash / PTY / LSP 会一起搬过去,无需 fork。

一条 Bash 命令背后的三个包

Service Definition dsh-shell

定义 Cordis 服务、Bash 请求与结果类型

Service Provider dsh-bash-local

在本地机器上真正执行命令

Consumer dsh-tool-bash

把能力暴露成模型可调用的 bash 工具

Provider 与 Consumer 互不依赖,两者都只依赖 Definition。 于是「把命令从本地挪到容器」只需要在 cordis.yml 里换一行。

03 / INSTALL

安装与启动

四条路径,按你的场景挑一条:只想用、想改、要嵌进程序、要跑在服务器

最快

npx 直接跑

装好 Node.js,一行启动本地 Web UI:

npx @deepseek-ai/dsh web

# 默认 http://127.0.0.1:3080,并自动开浏览器
# 只想起服务不弹浏览器:
npx @deepseek-ai/dsh web --no-open

# 换端口(URL 由 SSH 客户端/编辑器持有本地转发时只打印宿主机地址)
npx @deepseek-ai/dsh web --port 8080
从源码

仓库 checkout

要读代码、写插件、跟上游改动,走这条:

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

pnpm run build 准备产物;pnpm dsh 复用已构建产物,不再重新构建。

嵌程序

Python SDK

Python 3.10+,把 Agent 当成一个可调用的进程:

python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk

普通运行不需要系统 Node.js——wheel 里带了匹配的原生运行时。

无人值守

headless / ACP

一次性任务和自动化客户端,各有专属 profile:

# 跑一次、打印最终答案后退出
dsh --profile headless "run the tests"

# 通过 ACP stdio 为自动化客户端服务
dsh --profile acp

headless 不常驻服务器,适合 CI 与定时任务。

六个内置 profile

profile启动方式用途配置层重载
web dsh web 浏览器 UI,日常使用的主入口 实时重载
headless dsh --profile headless "job" 一次性运行,输出最终答案即退出 仅启动时
sdk dsh --profile sdk JSON-RPC stdio,给 SDK 客户端用 仅启动时
sdk-minimal dsh --profile sdk-minimal 极简独立配置树,不带 dsh-base,适合做最小可复现实验 仅启动时
acp dsh --profile acp Agent Client Protocol,给编辑器/自动化客户端 仅启动时
desktop Electron 应用持有 桌面端专用,CLI 会拒绝针对它的启动与插件管理
第一次别急着改代码。

启动后先做三件事:① 在设置 → 模型填入 API 密钥(立即生效,不用重启); ② 点选择工作区把项目目录加进来(没选工作区前输入框是灰的); ③ 发一句「总结这个仓库,指出主要包」,用来验证它是否真的读懂了你的项目。 它读错,说明规则文件或索引需要补。

04 / CLI & WORKSPACE

命令行与工作区

记住一条:启动时所在的目录,就是默认的 workspace 根目录

dsh 是唯一受支持的 Node 应用启动器。 所有子命令都由 profile 决定——profile 不是「模式开关」, 而是一棵由多个插件组合包按顺序叠加出来的配置树。

入口模式速查

命令用途注意
dsh --profile <name> 启动 $DSH_HOME/profiles/<name> 下的指定 profile 首次使用会从随附模板自动初始化
dsh --profile <name> --from-default-profile <tpl> 基于模板创建一个自定义 profile 并启动 适合给不同项目建不同组合
dsh plugin --profile <name> <pnpm args> 管理该 profile 的插件(转发给 pnpm) add / remove / 安装本地 bundle
dsh --profile web --dump-config 打印组合后的配置树,不启动 对照它就知道哪一行能被你的 patch 替换
dsh --profile web --dump-default-config 打印默认配置树(不含用户 patch) 排错时用来区分「默认如此」还是「我改坏的」
dsh --help 启动器自身的帮助 第一个不识别的 token 之后,参数归应用(如 --port

$DSH_HOME 里有什么

$DSH_HOME默认 ~/.dsh
settings.yaml模型页写入的同一份文档,可直接编辑
.credentials.yaml密钥只写不读,页面只拿到脱敏描述符
cordis.patch.ymlhome 级 patch,对本机所有 profile 生效
skills/用户级技能根目录(其 .system 子目录会被跳过)
profiles/
<name>/
package.json声明 dsh.profilebundles 顺序
cordis.patch.ymlprofile 级 patch
node_modules/pnpm 安装的树外插件
sessions/会话日志,默认 session.jsonl[.zstd],新格式走 session.vN.jsonl
配置层叠加顺序(后者覆盖前者):

profile 列出的各组合包 patch → profile 的 cordis.patch.yml → home 级 cordis.patch.yml → 最后是 --patch 指定的 overlay。 一条 patch 要么按 id 替换某个条目的整个 config,要么插入新条目。

05 / DAILY USE

日常使用:三种姿势

按「任务大小」选模式,不要拿大炮打蚊子

模式典型命令适合场景注意
交互式 Web UI主入口 dsh web 然后自然语言描述 探索性任务:边看边改、来回讨论、逐步收敛 需要审批的操作会先弹询问;步骤边界可控
一次性 headless可自动化 dsh --profile headless "job" 批量迁移、统一改法、CI 里跑固定任务 要写清验收条件,避免跑飞
程序化 SDK可集成 DeepSeekHarness(...).run(task) 把 Agent 嵌进你的流水线、后台服务或测试工具 用具名 session id 才能接着同一段对话

用 Python SDK 跑一个任务

from pathlib import Path
from deepseek_harness import DeepSeekHarness

workspace = Path("/absolute/path/to/disposable-workspace").resolve()
dsh_home  = Path("/absolute/path/to/example-dsh-home").resolve()

with DeepSeekHarness(
    provider="deepseek-official",
    model="deepseek-flash",
    max_tokens=49_152,
    cwd=str(workspace),
    dsh_home=str(dsh_home),      # 绝不静默读取 ~/.dsh
    profile="sdk-minimal",
) as harness:
    result = harness.run(
        "Inspect the repository and fix the failing tests.",
        session_id="example-001",
    )

print(result.final_response)

SDK 会延迟启动内置的 dsh --profile sdk-minimal 进程, 并在上下文管理器退出时关闭;进程在整个 with 块内被复用,不是每次调用都重启。

一条好任务的四要素

目标

做什么,一句话说清。别用「优化一下」这种无法验收的词。

范围

限定目录/模块。范围越小,改错的面越小,review 也越快。

验收

怎么算做完:跑通哪个测试、哪个接口返回什么、性能指标多少。

禁区

明确不许碰的东西:公共 API、数据库迁移脚本、CI 配置。

可复制的任务模板 照着填,成功率明显更高
目标:给订单模块补上「幂等下单」,重复请求返回同一订单号
范围:仅改 src/main/java/**/order 与对应测试目录
验收:新增单测通过,且 OrderControllerTest 全绿
禁区:不动数据库表结构,不动公共 Result 封装
完成后:输出改动的文件清单 + 为什么这么改

高频操作速记

dsh web起本地 Web UI,日常主入口
dsh web --port 8080换端口
dsh web --patch x.yml叠加一层配置 patch
dsh --dump-config看最终组合后的配置树
/plan切换计划模式(软性指引)
exit_plan_mode提交计划等待你批准
todo_write让 Agent 维护任务清单
skill按名加载一个技能正文
ask_user_question停下来向你提问
subagent把子任务派给子 Agent
job_list / job_kill管理后台任务
create_goal登记本会话的长期目标
dsh plugin --profile web add …安装插件到 profile
--from-default-profile复制模板建新 profile
保命三件套:commit / diff / 回滚。

交给它之前先 git commit 留一个干净基线,改完必看 diff, 不对劲就 git checkout .。 再加上 dsh 自己的沙箱与审批策略,你才敢真正放手。

06 / PLUGINS

写一个插件:从 20 行到可分发

这是 dsh 与「套壳 CLI」最大的分水岭——你是使用者,也是扩展者

第一步:最小插件长什么样

一个插件就是一个导出 apply 函数的 TypeScript 模块。 框架加载时调用 apply,传入上下文 ctx, 你通过 ctx 注册能力。

import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  // 必需依赖在此刻已经就绪
  console.log('[hello-plugin] plugin loaded!')
}

就这么多。没有注册表要填,没有插件清单文件——把文件挂进 cordis.yml 就能跑。

第二步:用 overlay 挂载它

01
写插件文件
scratch-plugin/src/my-plugin.ts
02
写 patch 层
scratch-plugin/cordis.yml
03
带 overlay 启动
dsh web --patch ./scratch-plugin/cordis.yml
# scratch-plugin/cordis.yml —— 一个 patch 数组
- insert:
    - id: hello
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
两个容易踩的点。

① 插件路径必须是绝对路径——patch 文件只贡献配置,不会改变 loader 解析模块时用的 profile 目录; ② 本地插件优先用 --patch 调试,要分发给别人才需要打包成组合包

第三步:让插件成为模型能用的工具

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 推导并校验 argsexecute 返回 output.schema 声明的规范值,output.render 再把它转成面向模型的内容。重载后直接对 Agent 说「用 greet 工具向 Ada 问好」即可验证。

插件配置:用 schema 而不是野对象

import Schema from '@deepseek-ai/schemastery'

export interface Config {
  greeting: string
  maxRetries: number
}

export const Config: Schema<Config> = Schema.object({
  greeting: Schema.string().default('Hello'),
  maxRetries: Schema.number().default(3),
})

export function apply(ctx: Context, config: Config) {
  console.log(config.greeting)   // 用户值,或 schema 默认值
}

cordis.yml 的插件行里加 config: 即可传参。 加载时 Cordis 会用 schema 校验配置并补齐默认值; 不要导出普通对象当 Config,它不满足 Cordis 要求的 Standard Schema 接口。

插件生命周期:Fiber 状态机

PENDING LOADING ACTIVE UNLOADING DISPOSED

PENDING 声明了但依赖未就绪 · LOADING 正在执行 apply · FAILED apply 抛异常 · 依赖服务消失时(例如提供方被替换)插件会 ACTIVE → DISPOSED,服务恢复后再自动加载回来。

五种事件分发模式

模式是否 await分发顺序有返回值典型用途
emit按注册顺序观察旁路通知、埋点
waterfall按注册顺序包裹环绕中间件:改写或短路决策
parallel全部监听器并行互不影响的并行副作用
serial按注册顺序串行必须按序收敛的决策
bail遇首个 bail 值即停「谁先认领谁负责」的短路判定

waterfall 监听器拿到 (...args, next):调用 next() 才委托下游;不调用直接返回就是短路—— 这正是策略插件声明「这个决策归我」的方式。

自动清理:不用手写 removeListener

export function apply(ctx: Context) {
  // 通过 ctx 注册的任何东西(事件监听、工具、定时器)在插件卸载时自动撤销
  ctx.effect(() => {
    const timer = setInterval(() => console.log('heartbeat'), 5000)
    return () => clearInterval(timer)   // 插件卸载时执行
  })
}

打包成组合包,让 profile 装它

概念声明位置回答的问题
组合包 bundle package.jsondsh.bundle 「这个包贡献什么?」——一个插入或覆盖插件行的 patch 文件
profile package.jsondsh.profile 「这套配置由哪些组合包按什么顺序组成?」——一个有序 bundles 列表
# 组合包的 package.json:声明一个 patch 文件
{ "name": "dsh-hello-plugin", "version": "0.1.0", "type": "module",
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }

# cordis.patch.yml:按包名引用,Node 才能解析到已安装代码
- insert:
    - id: hello
      name: dsh-hello-plugin

# 安装到某个 profile(管理命令才需要 pnpm,启动不需要)
dsh plugin --profile sdk-minimal add file:/absolute/path/to/hello-plugin

没有 dsh.bundle 声明的包也能装,但只作为普通依赖: dsh plugin 会打印警告且不激活任何层—— 如果这个包只是供别的插件 import,那正是你想要的效果

07 / SKILLS ENGINE

技能是怎么被「按需加载」的

技能 = 一份写给 Agent 的作业指导书;Harness 负责把它按需装进上下文

没有技能时,Agent 每次都靠「通用常识」硬扛,结果就是风格漂移、流程随机。 装上技能后,你的架构规范、SQL 审查口径、安全审计清单都会被固定下来—— 同一个任务,不同人跑,产出接近,这才是能进团队工作流的前提。

加载流程:目录先看,正文按需

技能根目录
.dsh/skills · .agents/skills
注册表合并
ctx.skills
目录注入上下文
只有 name + description
命中才读正文
skill({ name })

这个机制是「能装很多技能却不撑爆窗口」的原因: 会话里注入的只是已排序的 name 与 description不含正文、不含绝对路径;模型真的要用时才通过 skill 工具把正文读进来。 因此只改技能正文会影响后续调用,但不会改写历史对话。

技能根目录优先级

rank来源根目录典型用途
100project-dsh<projectRoot>/.dsh/skills项目专属,随仓库版本化
200project-agents<projectRoot>/.agents/skills跨工具共享的项目技能
300customConfig.customSkillDirs公司/团队私有技能目录
400user-dsh<DSH_HOME>/skills你个人的常用技能
500user-agents<AGENTS_HOME>/skills跨工具共享的个人技能(默认 ~/.agents
600bundled配置 bundledSkillDir 时生效随发行版打包的技能(默认禁用,需显式开启)

rank 越小越优先:项目根目录是「包含 .git 的最近祖先目录」, 找不到才用当前 cwd。重名时最近层直接赢;同一层内才按 rank、提供方顺序、本地顺序裁决。 技能名必须是 kebab-case^[a-z0-9]+(?:-[a-z0-9]+)*$), 支持目录包 <name>/SKILL.md 与扁平文件 <name>.md 两种形态。

两个开关:谁能调用这个技能

✅ 模型可调用
  • 出现在模型可见目录里,能被 skill 工具加载
  • frontmatter 不写 disable-model-invocation 即为开启
  • 适合「任务相关就自动用」的规范类技能
👤 仅用户可调用
  • 出现在人类可用的命令目录里,模型看不到
  • frontmatter 写 disable-model-invocation: true
  • 适合「只在你主动喊时才执行」的操作类技能

两个字段都设为 false 时,该技能只能由受信的代码 通过 ctx.skills.get() 获取——连模型和用户目录里都不会出现。 本地提供方读取省略的字段时默认都是 true

技能本体长什么样

# .dsh/skills/idempotent-api/SKILL.md
---
name: idempotent-api
description: 为写接口补幂等键,覆盖去重存储、并发与重放测试
whenToUse: 新增/修改任何会产生副作用的写接口时
---

# 幂等写接口

## 何时使用
新增 POST/PUT 接口,或改造已有接口使其可安全重试时。

## 步骤
1. 确认幂等键来源:客户端 Header 还是业务唯一键
2. 落库去重表,唯一索引兜底并发
3. 补三个测试:重复请求、并发请求、超时重试
4. 在 PR 描述里写明幂等键与过期策略

## 检查清单
- [ ] 重复请求返回同一业务结果,而非报错
- [ ] 并发场景有唯一索引或分布式锁兜底
- [ ] 幂等记录有 TTL,不会无限增长

description 会被注入目录(默认上限 500 字符), 写得能「路由」比写得长更重要;whenToUse 是额外的路由提示,不会替模型做决策。

✅ 该做的事
  • 技能按角色装:后端装架构 / SQL,安全装审计,前端装 SEO
  • 项目级技能写进 .dsh/skills/,随仓库一起版本化
  • 同一条技能先用小任务试跑,确认输出合口味再放进流水线
🚫 别做的事
  • 一口气挂十个技能——相互冲突时模型会左右为难
  • 把密钥、内网地址写进技能文件(正文是要被读进上下文的)
  • 指望技能绕过验证:该跑的测试一条都不能省
开发者常用 Skills 全清单(架构 / API / SQL / 安全 / 依赖 / SEO …) 含每个技能的核心功能与 GitHub 仓库地址,以及在本工具中的具体用法 →
08 / SAFETY & FLAGSHIP

安全边界与旗舰能力

敢放手的前提是想清楚「它能碰到什么」和「出格了怎么拦」

沙箱管文件效果,审批管是否放行

沙箱模式文件系统效果后台实现建议
read-only 只允许必需的接收器(如 /dev/null),拒绝写入 Linux bwrap / Landlock · macOS Seatbelt · Windows ACL 陌生仓库、只想让它读和回答时
workspace-write 允许在工作区根目录与后端承诺的临时区域内写入 同上 日常默认:够改代码,又出不了工作区
danger-full-access 绕过隔离,直接用原始 argv spawn 不调用沙箱服务 容器/一次性 checkout 里才用

注意网络与进程可见性不在这套词汇里——沙箱只管文件效果。 同类产品里已有实现把容器、microVM 当作同级 seam,而不是 ctx.sandbox 的一个提供方。

审批结果

四种结果,失败即拒绝

  • allowed-once —— 只授权这一次操作,不复用
  • rejected —— 明确拒绝
  • cancelled —— 请求被撤回
  • unavailable —— 没有应答者 / 应答者异常

后三种调用方一律按拒绝处理。没有应答者时默认拒绝,而不是默认放行—— 这是这套设计里最该被学走的一条。

审批策略

ask 与 never

  • ask —— 交给组合好的应答者链,无应答则 unavailable
  • never —— 确定性地直接返回 rejected,不打扰任何应答者

生效值取会话日志里最后一条 approval/policy 事件, 没有才回退到服务配置——所以重放会话能准确重建当时的策略, 而不是「按现在的配置猜当时发生了什么」。

权限预设:把两个开关捆成一个选择器

workspace-write 沙箱 workspace-write + 审批 ask 默认
danger-full-access 沙箱 danger-full-access + 审批 never 仅限容器
custom 两个 knob 分别被单独改过,不构成任何命名预设 派生状态

预设切换只记录意图并通过每个 knob 各自的规范 setter 写入; 真正执行、提示词叙述与回放读的仍是各自的折叠结果,所以预设层不拥有任何强制执行权

值得单独学的五个能力

计划模式

记录到日志的逐 Agent 协作状态,激活时每个请求都会带上部署持有的指引。它是软性指引——沙箱和审批才是强制限制,两者互不读写。

工具 exit_plan_mode · 命令 /plan

同会话目标

事件溯源的目标服务:每次获准的持久变更都递增修订号,调用方用 (id, revision) 做 CAS 修改,避免基于过期状态下决策。

工具 create_goal · update_goal · 阶段 active/paused/blocked

上下文压缩

先写 compaction/start 拿锁,再生成摘要、落幕、最后才 compaction/end 释放。中途崩溃会留下可检测的遗留锁,而不是一个谎称已完成的 end。

三步事件 start / summary / end 全部只写日志

Subagent 与 Agent Teams

六个兄弟提供方可供同一个上下文共存并按名注册:进程内 spawn/fork、ACP、Codex、Claude Code、dsh-sdk。实验性的 Agent Teams 之上还有持久 roster、任务板与 mailbox。

能力不足时会报 UNSUPPORTED_CAPABILITY,绝不静默降级

MCP 与工具治理

已配置的 MCP 服务器工具会以 mcp__<serverName>__<tool> 形式公开;stdio 桥接器在启动子进程前会剥离凭据类变量与所有 DSH_* 变量。

默认关闭,需要显式 --patch 才启用

工程集成:让它自己开评审会话

dsh 带了一个可选的 GitHub webhook overlay: 当已配置仓库的 PR 从 draft 变为 ready for review 时, 规则会在该仓库的 Web Workspace 下自动创建带标题的根会话,并启动只读评审提示词。

# 1) 生成一个高熵共享密钥,重启后继续用同一个值
export DSH_GITHUB_WEBHOOK_SECRET="$(openssl rand -hex 32)"

# 2) 指定要评审的本地 checkout,并挂上 overlay
export DSH_GITHUB_REVIEW_WORKSPACE=/path/to/your/repo
pnpm dsh web --patch apps/cli/config/examples/github-review/cordis.yml

# overlay 默认监听 127.0.0.1:3081,只注册 POST /github,其他路径 404
# 用 TLS 反代或 tunnel 把单个公网 URL 转发到这个 loopback 监听器
它是 fire-and-forget,别当队列用。

runtime 没有队列、重试、去重、执行状态或完成回执dispatch() 快照匹配规则、各自独立调度, 在任何回调结算前就返回。重复投递可能创建重复会话。 这个设计是有意的——它不假装自己是一个可靠消息中间件。

⚠️ 开发者预览状态

它明确声明「会有破坏兼容性的变更」。

项目处于 developer preview,正在快速迭代。 因此最佳实践是:把可复现的环境与配置一起版本化 (profile、patch、技能目录都进仓库), 升级前先 --dump-config 对比配置树差异, 并在一份一次性 checkout 里先验证。

09 / TIPS

使用技巧:把成功率压上去

以下十五条,多数来自「踩过坑才明白」

01

先计划,后执行

大改动先切计划模式 /plan,你过一遍再放行。计划阶段砍掉一半返工。

02

小步快跑

一次只改一件事,改完就验证。任务越大,跑偏越远、回滚代价越高、diff 越难看。

03

把测试当护栏

让它「先补测试再改代码」。测试既是验收标准,也是它自查的反馈信号。

04

上下文要喂,也要省

@文件 精确指定;垃圾上下文会带偏判断还烧额度。

05

把偏好写成技能

说完一次「我们不用 Lombok」就固化成技能或规则文件,省得每次重复、反复越界。

06

失败要复盘,不要重试

卡住时问它「刚才为什么失败」,比原样重跑十次有用得多;答案顺手沉淀进技能。

07

关键结论交叉验证

版本号、API 签名、配置项这类事实,让它给出处或直接跑命令验证——别信记忆。

08

按难度分配模型档位

给不同任务配不同模型档位:需要推理的交给强模型,机械迁移交给轻量模型,别一把梭。

09

安全边界提前划

开沙箱、开审批,只给必要目录权限。默认它不是恶意的,但也没有常识。

10

换任务就清上下文

上一件事的约束留在上下文里会把新任务带跑偏。换活就新开会话,或明确「忘掉刚才那些」。

11

让它自评,但别全信

让 Agent 自查有价值,但模型常高估自己。关键结论仍要人过一眼。

12

好用的提示词存起来

跑通一次的任务描述别扔,沉淀成模板或技能。下次同类任务直接复用,成功率立刻不一样。

13

给技能写「什么时候用」

description 是路由依据。写清适用场景,比写清实现细节更能让技能在对的时刻被选中。

14

配置也要进仓库

profile、cordis.patch.yml、项目级技能目录都提交上去。 项目在快速迭代,靠记忆复现环境迟早出错。

15

--dump-config 排错

「怎么不生效」多半是配置层被上层覆盖。打印最终配置树,比猜快十倍。

10 / PITFALLS

常见坑与速查卡

现象 → 原因 → 对策,照着排

现象多半是因为怎么解
输入框是灰的,打不了字 还没选工作区 点「选择工作区」把项目目录加进来并选中
改了配置就是不生效 被更上层的 patch 覆盖了 dsh --dump-config 看最终树,再决定 patch 写哪一层
插件加载了但工具没出现 目录重建 / 模型没重启 本地插件用 --patch 重载;确认 inject 声明的服务已就绪
本地插件报找不到模块 patch 里写了相对路径 插件路径必须是绝对路径;要分发则打包成组合包按包名引用
技能装了很多但不触发 description 写成了「实现说明」而非「适用场景」 whenToUse;检查技能名是否 kebab-case
同名技能行为不符合预期 被更高优先级的根目录抢了 按 rank 检查 .dsh/skills / .agents/skills / $DSH_HOME/skills
命令被审批拦住停在那里 沙箱模式较严或策略为 ask 但没人应答 确认应答者在线;无应答会返回 unavailable 并按拒绝处理
上下文超限报错 读了太多无关文件,或长会话没压缩 缩小任务范围;依赖压缩能力接管长会话
额度消耗异常快 轮次失控或陷入死循环 看日志定位哪个工具在空转;把机械任务换成便宜档位模型
webhook 创建了重复会话 它是 fire-and-forget,不做去重 预期内行为;如需幂等请在规则回调里自己做
升级后配置报错 developer preview 阶段的破坏性变更 升级前对比配置树差异,在一份一次性 checkout 里先验证

五条不该让它干的事

直接把生产库连接串给它连上去跑迁移

在没有回滚基线的仓库里跑大批量重构

把密钥写进技能文件、规则文件或 cordis.yml

danger-full-access 跑一份不是一次性 checkout 的仓库

让它「自动修复」线上故障却不留人工复核环节

一页速查(贴显示器版)

npx @deepseek-ai/dsh web
设置 → 模型填 key;选工作区
先问清楚上下文,再动手
目标 / 范围 / 验收 / 禁区,四要素写全
--patch 挂插件;技能放 .dsh/skills
测试先行,diff 必看
沙箱 + 审批,两个 knob 分开设
退git checkout . 随时止损

工具负责效率,判断权始终在你手里

接下来最值得投入的一件事:把你团队反复强调的那些规范,写成技能与 patch 层。