一切皆插件,连智能体循环都是
模型适配器、工具注册表、会话日志、沙箱、审批策略、甚至连 agent-loop 本身
都由插件提供。不存在「需要打补丁的特权内核」——
扩展 dsh 的方式是把插件挂在其他插件旁边。
一句话说清:把「大模型」变成「真能干活」的那层工程外壳,就是 Harness。
深度求索开源的 dsh 把这层外壳做到了极致——
没有特权内核,连智能体循环本身都是插件,任何一行都能被配置替换。
这里从 npx @deepseek-ai/dsh web 开始,
带你走完安装、插件、技能、沙箱、审批与工程集成的完整链路。
❯ 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 把「动手」做到底,把「拍板」留给你。
先建立心智模型,再谈命令,才不会用成「高级聊天框」
负责理解意图、拆解任务、写代码、做判断。它聪明,但只能「说」,摸不到你的文件系统。
读写文件、执行命令、调用工具、回传结果、控制上下文预算与终止条件——把「说」接成「做」。
把「怎么写架构、怎么审 SQL、怎么查依赖」这类固定套路固化下来,让大脑不必每次重新发明流程。
| 维度 | 纯对话式 AI | Harness 形态的 Agent |
|---|---|---|
| 输入 | 你贴过去的片段 | 整个仓库:目录树、依赖、配置、提交历史 |
| 输出 | 一段需要你复制粘贴的文本 | 直接落到文件系统的 diff,可跑、可测、可回滚 |
| 闭环 | 贴 → 试 → 报错 → 再贴(人工当回路) | 改 → 跑测试 → 看报错 → 自己再改(机器当回路) |
| 稳定性 | 依赖你的提问水平 | 依赖你给的技能、约束与验证手段 |
| 最合适的事 | 答疑、解释、给思路 | 跨文件重构、批量迁移、补测试、修 CI、写文档 |
它的甜区是边界清晰、有验证手段的任务。任务越模糊、越依赖业务上下文,你越该先把它拆小、把验收标准写清楚,再交给它。
它不是又一个「套壳 CLI」,架构选择直接决定你能改到什么程度
模型适配器、工具注册表、会话日志、沙箱、审批策略、甚至连 agent-loop 本身
都由插件提供。不存在「需要打补丁的特权内核」——
扩展 dsh 的方式是把插件挂在其他插件旁边。
提示词片段、工具 schema、适配器、事件监听,全部通过 ctx.effect() 或
ctx.on() 安装。插件卸载时,这些注册按注册顺序自动撤销,
你不需要手写 removeListener。
一项能力被拆成三个角色:Service Definition(声明接口)、 Service Provider(实现)、Consumer(面向模型的工具)。 换掉 Provider,Definition 与工具都不用改。
dsh-shell
定义 Cordis 服务、Bash 请求与结果类型
dsh-bash-local
在本地机器上真正执行命令
dsh-tool-bash
把能力暴露成模型可调用的 bash 工具
Provider 与 Consumer 互不依赖,两者都只依赖 Definition。
于是「把命令从本地挪到容器」只需要在 cordis.yml 里换一行。
四条路径,按你的场景挑一条:只想用、想改、要嵌进程序、要跑在服务器
装好 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
要读代码、写插件、跟上游改动,走这条:
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 3.10+,把 Agent 当成一个可调用的进程:
python -m venv .venv
. .venv/bin/activate
python -m pip install deepseek-harness-sdk
普通运行不需要系统 Node.js——wheel 里带了匹配的原生运行时。
一次性任务和自动化客户端,各有专属 profile:
# 跑一次、打印最终答案后退出
dsh --profile headless "run the tests"
# 通过 ACP stdio 为自动化客户端服务
dsh --profile acp
headless 不常驻服务器,适合 CI 与定时任务。
| 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 密钥(立即生效,不用重启); ② 点选择工作区把项目目录加进来(没选工作区前输入框是灰的); ③ 发一句「总结这个仓库,指出主要包」,用来验证它是否真的读懂了你的项目。 它读错,说明规则文件或索引需要补。
记住一条:启动时所在的目录,就是默认的 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默认 ~/.dshsettings.yaml模型页写入的同一份文档,可直接编辑.credentials.yaml密钥只写不读,页面只拿到脱敏描述符cordis.patch.ymlhome 级 patch,对本机所有 profile 生效skills/用户级技能根目录(其 .system 子目录会被跳过)profiles/<name>/package.json声明 dsh.profile 与 bundles 顺序cordis.patch.ymlprofile 级 patchnode_modules/pnpm 安装的树外插件sessions/会话日志,默认 session.jsonl[.zstd],新格式走 session.vN.jsonl
profile 列出的各组合包 patch → profile 的 cordis.patch.yml →
home 级 cordis.patch.yml → 最后是 --patch 指定的 overlay。
一条 patch 要么按 id 替换某个条目的整个 config,要么插入新条目。
按「任务大小」选模式,不要拿大炮打蚊子
| 模式 | 典型命令 | 适合场景 | 注意 |
|---|---|---|---|
| 交互式 Web UI主入口 | dsh web 然后自然语言描述 |
探索性任务:边看边改、来回讨论、逐步收敛 | 需要审批的操作会先弹询问;步骤边界可控 |
| 一次性 headless可自动化 | dsh --profile headless "job" |
批量迁移、统一改法、CI 里跑固定任务 | 要写清验收条件,避免跑飞 |
| 程序化 SDK可集成 | DeepSeekHarness(...).run(task) |
把 Agent 嵌进你的流水线、后台服务或测试工具 | 用具名 session id 才能接着同一段对话 |
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叠加一层配置 patchdsh --dump-config看最终组合后的配置树/plan切换计划模式(软性指引)exit_plan_mode提交计划等待你批准todo_write让 Agent 维护任务清单skill按名加载一个技能正文ask_user_question停下来向你提问subagent把子任务派给子 Agentjob_list / job_kill管理后台任务create_goal登记本会话的长期目标dsh plugin --profile web add …安装插件到 profile--from-default-profile复制模板建新 profile
交给它之前先 git commit 留一个干净基线,改完必看 diff,
不对劲就 git checkout .。
再加上 dsh 自己的沙箱与审批策略,你才敢真正放手。
这是 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 就能跑。
scratch-plugin/src/my-plugin.ts
scratch-plugin/cordis.yml
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 推导并校验
args;execute 返回
output.schema 声明的规范值,output.render
再把它转成面向模型的内容。重载后直接对 Agent 说「用 greet 工具向 Ada 问好」即可验证。
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 接口。
PENDING 声明了但依赖未就绪 · LOADING 正在执行 apply · FAILED apply 抛异常 · 依赖服务消失时(例如提供方被替换)插件会 ACTIVE → DISPOSED,服务恢复后再自动加载回来。
| 模式 | 是否 await | 分发顺序 | 有返回值 | 典型用途 |
|---|---|---|---|---|
emit | 否 | 按注册顺序观察 | 否 | 旁路通知、埋点 |
waterfall | 否 | 按注册顺序包裹 | 是 | 环绕中间件:改写或短路决策 |
parallel | 是 | 全部监听器并行 | 否 | 互不影响的并行副作用 |
serial | 是 | 按注册顺序串行 | 是 | 必须按序收敛的决策 |
bail | 否 | 遇首个 bail 值即停 | 是 | 「谁先认领谁负责」的短路判定 |
waterfall 监听器拿到 (...args, next):调用
next() 才委托下游;不调用直接返回就是短路——
这正是策略插件声明「这个决策归我」的方式。
export function apply(ctx: Context) {
// 通过 ctx 注册的任何东西(事件监听、工具、定时器)在插件卸载时自动撤销
ctx.effect(() => {
const timer = setInterval(() => console.log('heartbeat'), 5000)
return () => clearInterval(timer) // 插件卸载时执行
})
}
| 概念 | 声明位置 | 回答的问题 |
|---|---|---|
| 组合包 bundle | package.json 的 dsh.bundle |
「这个包贡献什么?」——一个插入或覆盖插件行的 patch 文件 |
| profile | package.json 的 dsh.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,那正是你想要的效果。
技能 = 一份写给 Agent 的作业指导书;Harness 负责把它按需装进上下文
没有技能时,Agent 每次都靠「通用常识」硬扛,结果就是风格漂移、流程随机。 装上技能后,你的架构规范、SQL 审查口径、安全审计清单都会被固定下来—— 同一个任务,不同人跑,产出接近,这才是能进团队工作流的前提。
这个机制是「能装很多技能却不撑爆窗口」的原因:
会话里注入的只是已排序的 name 与 description,
不含正文、不含绝对路径;模型真的要用时才通过
skill 工具把正文读进来。
因此只改技能正文会影响后续调用,但不会改写历史对话。
| rank | 来源 | 根目录 | 典型用途 |
|---|---|---|---|
| 100 | project-dsh | <projectRoot>/.dsh/skills | 项目专属,随仓库版本化 |
| 200 | project-agents | <projectRoot>/.agents/skills | 跨工具共享的项目技能 |
| 300 | custom | Config.customSkillDirs | 公司/团队私有技能目录 |
| 400 | user-dsh | <DSH_HOME>/skills | 你个人的常用技能 |
| 500 | user-agents | <AGENTS_HOME>/skills | 跨工具共享的个人技能(默认 ~/.agents) |
| 600 | bundled | 配置 bundledSkillDir 时生效 | 随发行版打包的技能(默认禁用,需显式开启) |
rank 越小越优先:项目根目录是「包含 .git 的最近祖先目录」,
找不到才用当前 cwd。重名时最近层直接赢;同一层内才按 rank、提供方顺序、本地顺序裁决。
技能名必须是 kebab-case(^[a-z0-9]+(?:-[a-z0-9]+)*$),
支持目录包 <name>/SKILL.md 与扁平文件
<name>.md 两种形态。
skill 工具加载disable-model-invocation 即为开启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 是额外的路由提示,不会替模型做决策。
.dsh/skills/,随仓库一起版本化敢放手的前提是想清楚「它能碰到什么」和「出格了怎么拦」
| 沙箱模式 | 文件系统效果 | 后台实现 | 建议 |
|---|---|---|---|
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 —— 交给组合好的应答者链,无应答则 unavailablenever —— 确定性地直接返回 rejected,不打扰任何应答者
生效值取会话日志里最后一条 approval/policy 事件,
没有才回退到服务配置——所以重放会话能准确重建当时的策略,
而不是「按现在的配置猜当时发生了什么」。
workspace-write
沙箱 workspace-write + 审批 ask
默认
danger-full-access
沙箱 danger-full-access + 审批 never
仅限容器
custom
两个 knob 分别被单独改过,不构成任何命名预设
派生状态
预设切换只记录意图并通过每个 knob 各自的规范 setter 写入; 真正执行、提示词叙述与回放读的仍是各自的折叠结果,所以预设层不拥有任何强制执行权。
记录到日志的逐 Agent 协作状态,激活时每个请求都会带上部署持有的指引。它是软性指引——沙箱和审批才是强制限制,两者互不读写。
事件溯源的目标服务:每次获准的持久变更都递增修订号,调用方用 (id, revision) 做 CAS 修改,避免基于过期状态下决策。
先写 compaction/start 拿锁,再生成摘要、落幕、最后才 compaction/end 释放。中途崩溃会留下可检测的遗留锁,而不是一个谎称已完成的 end。
六个兄弟提供方可供同一个上下文共存并按名注册:进程内 spawn/fork、ACP、Codex、Claude Code、dsh-sdk。实验性的 Agent Teams 之上还有持久 roster、任务板与 mailbox。
已配置的 MCP 服务器工具会以 mcp__<serverName>__<tool> 形式公开;stdio 桥接器在启动子进程前会剥离凭据类变量与所有 DSH_* 变量。
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 监听器
runtime 没有队列、重试、去重、执行状态或完成回执:
dispatch() 快照匹配规则、各自独立调度,
在任何回调结算前就返回。重复投递可能创建重复会话。
这个设计是有意的——它不假装自己是一个可靠消息中间件。
项目处于 developer preview,正在快速迭代。
因此最佳实践是:把可复现的环境与配置一起版本化
(profile、patch、技能目录都进仓库),
升级前先 --dump-config 对比配置树差异,
并在一份一次性 checkout 里先验证。
以下十五条,多数来自「踩过坑才明白」
大改动先切计划模式 /plan,你过一遍再放行。计划阶段砍掉一半返工。
一次只改一件事,改完就验证。任务越大,跑偏越远、回滚代价越高、diff 越难看。
让它「先补测试再改代码」。测试既是验收标准,也是它自查的反馈信号。
用 @文件 精确指定;垃圾上下文会带偏判断还烧额度。
说完一次「我们不用 Lombok」就固化成技能或规则文件,省得每次重复、反复越界。
卡住时问它「刚才为什么失败」,比原样重跑十次有用得多;答案顺手沉淀进技能。
版本号、API 签名、配置项这类事实,让它给出处或直接跑命令验证——别信记忆。
给不同任务配不同模型档位:需要推理的交给强模型,机械迁移交给轻量模型,别一把梭。
开沙箱、开审批,只给必要目录权限。默认它不是恶意的,但也没有常识。
上一件事的约束留在上下文里会把新任务带跑偏。换活就新开会话,或明确「忘掉刚才那些」。
让 Agent 自查有价值,但模型常高估自己。关键结论仍要人过一眼。
跑通一次的任务描述别扔,沉淀成模板或技能。下次同类任务直接复用,成功率立刻不一样。
description 是路由依据。写清适用场景,比写清实现细节更能让技能在对的时刻被选中。
profile、cordis.patch.yml、项目级技能目录都提交上去。
项目在快速迭代,靠记忆复现环境迟早出错。
--dump-config 排错「怎么不生效」多半是配置层被上层覆盖。打印最终配置树,比猜快十倍。
现象 → 原因 → 对策,照着排
| 现象 | 多半是因为 | 怎么解 |
|---|---|---|
| 输入框是灰的,打不了字 | 还没选工作区 | 点「选择工作区」把项目目录加进来并选中 |
| 改了配置就是不生效 | 被更上层的 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 的仓库
让它「自动修复」线上故障却不留人工复核环节