面向 DeepSeek Harness(DSH)的实操教程:项目配置、Skill 写法、Agent 边界、任务拆分、多 Agent 协同。所有规则都对着 DSH 源码或在本机运行时实跑验证过;没能验证的条目都明确标注了「未验证」和自测方法。
| 你的情况 | 读这个 |
|---|---|
| 第一次用 DSH,想知道该怎么使力 | 主教程 第 0 章 心智模型 |
| 想马上把一个仓库接上 | 配方 1:让一个新仓库"接上" |
| 想固化团队流程 | 第 2 章 写好 Skill |
| 担心 agent 越界 | 第 3 章 Agent 边界 |
| 任务一大就翻车 | 第 4 章 任务拆分 |
| 想并行推进多条线 | 第 5 章 多 Agent 协同 |
| 会话越长越笨 | 第 6 章 上下文工程 |
| 出问题了 | 第 8.2 节 症状→根因→处理 |
vibe-coding-guide.md 主教程(第 0–8 章)
appendix/copy-paste-recipes.md 10 条 5 分钟配方 + 常用命令一览
validate-guide.py 校验脚本(见下节"验证方式")
examples/
profile-patch.example.yml 插件与权限配置补丁(已用 --dump-config 实跑验证)
settings.example.yaml 用户设置(模型/权限/子 agent 模型/主题)
AGENTS.example.md 项目级工作指令模板
skills/project-standards/SKILL.md Skill 模板 A:规范类
skills/run-tests/SKILL.md Skill 模板 B:流程类
agent-presets/readonly-agent/ 自定义 agent preset:只读评审(不含 write/edit)
教程包里的示例做了三层校验,都可复现:
YAML/JSON 语法 + Skill frontmatter 规则 + 链接/锚点
& 'D:\env\python\python.exe' 'E:\dsh\chat\vibe-coding-guide\validate-guide.py'
校验内容:YAML/JSON 可解析(容忍 !!js 标签)、补丁顶层是数组、skill name 为 kebab-case 且与目录名一致、description 非空且 ≤500 字符、全文链接与章节锚点可达。
profile 补丁实跑
& 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd' --profile web `
--patch 'E:\dsh\chat\vibe-coding-guide\examples\profile-patch.example.yml' --dump-config
退出码 0,且补丁值出现在 dump 里,说明每个 id 与 config 键都被当前版本接受。
skill 规则来源:packages/skill/skill-filesystem(frontmatter 解析)、packages/skill/tool-skill(目录注入与 500 字符截断)。
--dump-config 是唯一可信的"我现在到底跑的是什么"。config 全量覆盖,没写的键回到默认值;没有 remove 语法,停用一行写 disabled: true;空补丁文件必须写 []。dsh --profile web)D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd(未加入 PATH)C:\Users\<你>\.dsh(可用 DSH_HOME 环境变量覆盖)$DSH = 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd'
& $DSH --profile web --dump-config # 当前生效的完整配置树
& $DSH --profile web --dump-default-config # 只看官方默认值
面向 DeepSeek Harness(下称 DSH)的实战手册:怎么配项目、怎么写 skill、怎么划边界、怎么拆任务、怎么让多个 agent 协同。
教程里的每一条规则都能在 DSH 源码或你的本机运行时里找到出处。凡是我没能验证的,都会明确标「未验证」,并给出自测方法——请把它们当假设,不要当结论。
| 章节 | 你学到什么 | 什么时候读 |
|---|---|---|
| 第 0 章 心智模型 | 四个杠杆、黄金循环、三条反直觉原则 | 先读,10 分钟 |
| 第 1 章 项目配置 | profile 补丁、settings、agent preset、MCP | 第一次接入一个仓库 |
| 第 2 章 写好 Skill | frontmatter 精确规范、发现优先级、失败模式 | 想把团队经验固化下来 |
| 第 3 章 Agent 边界 | 权限预设、沙箱真实语义、读后写、plan mode、AGENTS.md | 让 agent 跑得久一点而不出事 |
| 第 4 章 任务拆分 | 拆分判据、七种手法、规模选择决策树 | 任务一大就翻车的时候 |
| 第 5 章 多 Agent 协同 | subagent / fork / workflow、六种拓扑、上限 | 想并行推进多条线 |
| 第 6 章 上下文工程 | 压缩、截断、转存、按需读取 | 会话越长越笨的时候 |
| 第 7 章 端到端实战 | 一个需求从 0 到验收的完整走法 | 想直接抄流程 |
| 第 8 章 验收与排障 | 症状→根因→处理表、速查卡 | 出问题时 |
| 附录:复制粘贴配方 | 10 个 5 分钟配方 | 想立刻动手 |
可复制的示例文件(正文里会逐个引用):
examples/
profile-patch.example.yml # → ~/.dsh/profiles/web/cordis.patch.yml
settings.example.yaml # → ~/.dsh/settings.yaml
AGENTS.example.md # → <repo>/AGENTS.md
skills/project-standards/SKILL.md # → <repo>/.dsh/skills/project-standards/SKILL.md
skills/run-tests/SKILL.md # → <repo>/.dsh/skills/run-tests/SKILL.md
agent-presets/readonly-agent/ # → ~/.dsh/.agent-presets/readonly-agent/
这四张表是全文的压缩版,细节在对应章节。
| 想改的东西 | 放哪 | 生效方式 |
|---|---|---|
| 工具行为、插件参数、权限预设表 | <你的仓库>/.dsh/… 之上一层的 ~/.dsh/profiles/web/cordis.patch.yml |
该 profile 配了 patchReload: live 时热重载 |
| 想对所有 profile 生效的补丁 | ~/.dsh/cordis.patch.yml |
优先级高于 profile 补丁 |
| 默认模型、默认权限档、子 agent 可用模型、主题语言 | ~/.dsh/settings.yaml |
热重载;只影响新建会话 |
| 项目该怎么被改、怎么验收 | <repo>/AGENTS.md |
首个请求注入;子目录文件在触碰后补注入 |
| 团队流程与判定标准 | <repo>/.dsh/skills/<name>/SKILL.md |
按需加载(模型的 skill 工具或用户 /name) |
| 一种专用 agent(工具集 + 人设) | ~/.dsh/.agent-presets/<id>/ |
空白会话里选择 |
| 任务风险 | 权限预设 | 沙箱 | 审批 | 还要做什么 |
|---|---|---|---|---|
| 只读分析、评审、答疑 | audit-only(自定义) |
read-only |
ask |
让 agent 先给结论再给证据 |
| 日常改代码、跑测试 | workspace-write |
工作区 + 会话私有临时目录 | ask |
先 plan 再动手;AGENTS.md 写清禁止项 |
| 可信仓库里的大批量重构 | danger-full-access |
不隔离 | never |
必须配合版本控制与可回滚的工作区 |
| 摸不准的新仓库 | read-only 先侦察 |
read-only |
ask |
侦察完再切档 |
| 规模 | 用哪个机制 |
|---|---|
| 一次对话能完成 | 直接做;动手前先读现状 |
| 多步骤、要人确认方案 | /plan → exit_plan_mode 提交计划 |
| 跨多轮、有明确完成定义 | create_goal + update_goal |
| 需要可见进度 | todo_write(全量替换,in_progress 可真并行) |
| 独立、可并行的方向 | subagent(后台默认)或 subagent_fork(需要父会话上下文) |
| 几十个同构对象要扫 | workflow 脚本扇出 |
| 拓扑 | 适用 | 关键纪律 |
|---|---|---|
| 扇出—扇入 | 多角度研究、多目录审计 | 每个子任务给固定输出格式 |
| 流水线 | 迁移、逐文件改造 | pipeline 无屏障;每阶段带证据 |
| 生成者—审查者 | 高风险改动 | 审查者必须看原始证据,不是看生成者的总结 |
| 计划—执行分离 | 需求模糊 | 计划产出物是"可验收的步骤",不是技术方案散文 |
| 后台并行 + 主线继续 | 长耗时命令、外部依赖 | 别轮询,等通知;别重复已委派的工作 |
| 大规模扫描 | 上百文件 | 用 workflow,注意 maxTotalAgents = 1000 上限 |
本教程的机器基准:Windows + DSH Web GUI(
dsh --profile web,即你正在看的页面)。dsh命令在安装目录里,没进 PATH:& "$env:USERPROFILE\.dsh\..\..\..\..\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd" --help # 示意,请用下面那种写法 & 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd' --profile web --dump-config
$DSH_HOME默认是C:\Users\<你>\.dsh;可以用同名环境变量覆盖。
传统编码里,你的产出是代码。vibe coding 里,你的产出是边界 + 验收标准,代码由 agent 写。于是你的力气应该花在四件事上:
| 杠杆 | 你控制的东西 | 失效时的症状 |
|---|---|---|
| 上下文 | agent 此刻能看到什么 | 它"忘了"约定、反复问同一件事 |
| 边界 | 它能碰什么、什么需要你确认 | 它改了不该改的文件、越权写系统目录 |
| 拆分 | 一个工作单元有多大 | 任务做到一半失去方向,改动无法验证 |
| 协同 | 谁来做、结果怎么回流 | 你成了人肉消息总线,或两个 agent 互相覆盖 |
一句话判断你有没有用好 DSH:你写的 prompt 越来越短,而仓库里的 AGENTS.md / skill / preset 越来越多。因为前者每次都要重说,后者说一次就一直在。
不管是人做还是 agent 做,一个健康的工作单元都是这五步:
① 读现状 → ② 给计划 → ③ 小步改 → ④ 立即验证 → ⑤ 记录/沉淀
(证据) (可验收) (最小改动) (真实输出) (进 AGENTS.md / skill)
最常见的失败是跳过 ① 和 ④:不看现状就开始写(于是重复造轮子、改错地方),改完不验证就宣布完成(于是"应该可以"成了结论)。教程后面反复出现的一句话是:只报实际跑过的结果。
一个工具出现在模型面前,不等于它能成功执行;反过来,工具被拿掉,模型连"想调用"都不存在这个概念。DSH 把这两件事分开:
toolFilter 屏蔽、preset 是否挂载了这个插件行。所以"我怕它乱删文件"这句话有两个完全不同的解法:不挂载 write/edit 插件行(能力层面),或者切到 read-only 沙箱(权限层面)。两者都要用才稳。
DSH 的 profile 补丁按 id 命中一行,命中的那一行整个 config 被替换。不会和你原来的值深合并。
后果:如果你只想把 maxInlineBytes 从 50000 改成 20000,你必须把那行原来关心的所有键都重新写清楚——否则没写的键会回到 schema 默认值。写补丁前先看当前行长什么样:
& 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd' --profile web --dump-config > dump.yml
# 然后在 dump.yml 里找到那个 id,把它附近的 config 整块抄下来再改
另外两个坑:补丁没有 remove 语法(停用一行写 disabled: true);补丁文件如果只有注释、没有 [],启动会失败。
委派深度是有上限的,默认 3,而且宿主可以把它压到 1。在当前这台机器上实测就是 1:我让一个子 agent 再去委派,返回的是
Error: subagent depth 2 exceeds maxDepth 1
值得注意的是:超限时工具依然可见,报错发生在调用时。所以你会看到子 agent"试图"委派然后失败,而不是干脆没有这个工具。
实践含义:委派提示词必须写成"一个自包含的工人任务",不要写"你再去拆一下"。需要两级以上的结构,就由主 agent 用 workflow 脚本直接编排,而不是让中间层递归。
| 术语 | 含义 |
|---|---|
| profile | 一种启动形态(web / headless / sdk / acp),本质是一份分层补丁的组装结果 |
| 补丁 / patch | 一个 YAML 数组,按 id 覆盖或插入插件行 |
| bundle | 官方提供的补丁层,如 @deepseek-ai/dsh-base、dsh-web-app |
| settings | ~/.dsh/settings.yaml,按命名空间分段的用户设置(热重载) |
| preset | 一份"某个 agent 由哪些工具/提示词/skill 组成"的目录定义 |
| skill | 一段按需加载的指令(SKILL.md),不增加任何能力 |
| AGENTS.md | 工作区指令文件,随会话自动注入 |
| subagent | 由当前 agent 创建的、独立上下文的子 agent |
| workflow | 一段 JS 编排脚本,用来成批调度子 agent |
| job | 后台作业(长命令或后台子 agent) |
| goal / todo | 跨轮目标 / 当前执行清单 |
D:\project\github\deepseek-harness 的实现或文档。① bundle 层 @deepseek-ai/dsh-base → dsh-web-app (官方默认值,你不改)
② profile 层 ~/.dsh/profiles/web/cordis.patch.yml (你的主要战场)
③ home 层 ~/.dsh/cordis.patch.yml (对所有 profile 生效)
④ 调用层 dsh --profile web --patch ./extra.yml (一次性实验)
后写的层按行 id 覆盖先写的层,行内 config 整体替换。查看任意一层的真实结果:
# 目前生效的完整插件树(①–④)
& 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd' --profile web --dump-config
# 只看 bundle 默认值(①),用来对比"我到底改动了什么"
& 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd' --profile web --dump-default-config
# 带某份实验补丁看效果(推荐先用这个验证,再写进 profile)
& 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd' --profile web `
--patch 'E:\dsh\chat\vibe-coding-guide\examples\profile-patch.example.yml' --dump-config
最后一条我实测过(exit=0),所以 examples/profile-patch.example.yml 里出现的键都是当前版本真实接受的。
改哪些东西要重启?
| 改动 | 生效方式 |
|---|---|
profile 的 cordis.patch.yml 内容 |
patchReload: live 时热重载(随附 web profile 就是 live) |
补丁里增删插件包(dsh plugin ... add) |
需要重启 profile |
settings.yaml |
热重载 |
.agent-presets/<id>/ 里的文件 |
每次读名单都重新扫盘;但已按旧版组装的会话继续用旧组装 |
skill 的 SKILL.md |
目录条目变更会被监听;正文每次加载都重读 |
- id: tool-web # 定位一行;命中就替换它的 config
name: '@deepseek-ai/dsh-tool-web' # 必填,插件包名
config: # 整体替换,不是深合并
search: true
fetch: true
searchTimeoutMs: 60000
disabled: false # 想停用一行就用它,没有 remove 语法
- insert: # 往插件树里插入新行(例如接一个 MCP server)
- id: mcp-filesystem
name: '@deepseek-ai/dsh-mcp-client'
config: { ... }
实用细节:
[],只写注释的文件会让启动失败。!!js 表达式只在两个地方被求值:config 里的值和 disabled。写成 !js(单叹号)不合法。id 时你可能以为生效了其实没有。改完必须 --dump-config 确认。insert 进来的行可以被后面的条目按 id 引用。路径 <你>\.dsh\settings.yaml(settings.json/.yml 也行)。结构是「命名空间 → 用户配置」,解析顺序是:schema 默认值 → 插件组合里的 base → 你的用户段。
可直接复制 examples/settings.example.yaml,核心几段:
agent-default-model: # 新会话默认模型
provider: deepseek-official
model: deepseek-flash
reasoningEffort: high
permission: # 新会话默认权限预设
defaultPreset: workspace-write
subagent-model-selection: # 允许子 agent 使用哪些模型路由
enabled: true
allowedModels:
- provider: deepseek-official
model: deepseek-flash
agent-presets: # 新会话默认 preset
default: standard
agent-loop:
maxParallelToolCalls: 8 # 一轮内最多并行几个工具调用
可用的命名空间(源码确认存在的):agent-default-model、agent-loop、permission、subagent-model-selection、agent-presets、ui-theme、locale、ui-conversation、ui-chat、shell、llm-deepseek、llm-pi-ai、web-search-deepseek、ui-onboarding。
两个必须记住的性质:
permission.defaultPreset 只在你新开一个会话时被读一次;正在跑的会话保持它原来的档位(这个设计是对的:中途换权限会让已经发生的工具调用变得不可解释)。~/.dsh/.credentials.yaml,环境变量优先。在 Web GUI 下,每个浏览器会话都不共享进程级工具集,而是从一个 preset 组装自己:它的工具、提示词段落、skill。所以"给 agent 收窄能力"最自然的位置就是 preset。
~/.dsh/.agent-presets/<id>/
preset.yml # 展示用:name / description / order
agent.cordis.yml # 组合:一个插件行列表(跟 profile 补丁同一种语法)
规则:
id = 目录名,必须匹配 [a-z0-9][a-z0-9-]*,不能和随附的 standard / ptc / cordis / minimal 重名。agent.cordis.yml 的目录会以 "broken" 状态列出并给出原因,不会被静默跳过。examples/agent-presets/readonly-agent/——一个刻意不挂载 write/edit 的只读评审 agent。常用玩法:
| 目标 | 做法 |
|---|---|
| 只读评审 agent | 不挂 tool-fs 的写能力;更彻底的做法是配合 read-only 沙箱 |
| 极简 agent(省 token) | 参考随附 minimal:persona.complete: true 让固定提示词成为整个系统提示词 |
| 专用模型 | 在 preset 里给 agent-default-model 行换 provider/model,或用子 agent 的 agentOptions |
| 加一个新 preset | 复制现有 preset 目录再改(复制会保留来源描述,但 id 是自己的) |
# 不动任何已安装配置,先试一份补丁
& '...dsh.cmd' --profile web --patch .\extra.yml --dump-config
# 从随附模板派生一个自己的 profile(目标名不能是已有目录、也不能是官方 profile 名)
& '...dsh.cmd' --profile rescue --from-default-profile web
& '...dsh.cmd' --profile rescue --dump-config
--from-default-profile 只复制模板的 bundle 列表与 patchReload 值,不复制你本地 profile 的依赖或补丁;如果 rescue 目录已存在会直接拒绝(不会覆盖,也不会顺带启动)。用 --dump-config + 这个选项可以"只创建、不启动"。
两个容易吃亏的细节:
dsh --profile web --port 8080 里的 --port 是 web app 的参数,而 dsh --help(没有 profile 可转交)打印的是启动器自己的帮助。--dump-config 不 boot 应用,因此它显示的是"app 参数还没被解析"的组装结果;带 app 参数调用它会被拒绝。profile 目录长这样:
{
"name": "dsh-profile-web",
"private": true,
"dependencies": {},
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"], "patchReload": "live" } }
}
bundles 是有序的补丁层列表。加一个官方 bundle 用:
& '...dsh.cmd' plugin --profile web add @deepseek-ai/dsh-subagent-codex
# 需要 pnpm 在 PATH 上;装完必须重启该 profile
装完之后 bundle 内的 provider 只是"可用",不等于对模型可见:要让新的 agent 用上它,还得在对应 preset 里启用那一行(随附 preset 里这类行通常是 disabled: true,去掉即可)。
| 行 id | 键(默认) | 什么时候调 |
|---|---|---|
tool-fs |
readLimit 2000、readMaxLineLength 2000、readMaxBytes 51200 |
仓库里文件很宽(长行)时调大行宽,否则读进来全是截断 |
tool-fs-search |
globMaxResults、grepMaxMatches、sampleOverCapGlobResults false |
大仓库里 glob 命中过多时收紧 |
tool-result-pruner |
thresholdChars 8192、headChars 4096、tailChars 1024 |
日志型工具输出太多,想更早裁掉 |
spill-policy |
maxInlineBytes 50000(省略即关闭) |
想让超长结果自动落盘、只留预览 |
compaction-basic |
auto true、thresholdRatio 0.8、retainRatio 0.16 |
长会话想更早/更晚自动压缩 |
repeat-tool-reminder |
thresholds [3,5,8]、exclude [] |
agent 老在原地打转时把阈值调小 |
agent-instructions |
maxBytes 65536(必填) |
指令文件多,想加大预算 |
tool-web |
searchTimeoutMs 60000、fetchTimeoutMs 30000、fetchMaxOutputChars 200000 |
内网抓取慢、或网页太大 |
tool-subagent |
maxDepth 3、backgroundMode continuable、modelSelectionSettings false |
想禁掉委派写 0;想让子 agent 指定模型要开 modelSelectionSettings |
tool-workflow |
toolName workflow、maxResultChars 50000 |
脚本结果太长被截断 |
tool-todo |
allowParallelInProgress true |
不允许并行推进时改 false |
jobs(dsh-jobs-local) |
maxConcurrentJobsPerOwner 10 |
并发后台作业撞上限时调大 |
output-retention容易踩坑:它是库不是可配置的插件行,配置里根本没有这一项。真正管截断的是tool-result-pruner和spill-policy。
一个 id 一个服务器,工具名会被命名空间化成 mcp__<serverName>__<原工具名>。serverName 必填、匹配 [A-Za-z0-9_-]{1,32}、在同一注册作用域内唯一。
- insert:
- id: mcp-filesystem
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: fs
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-filesystem', 'E:\dsh\chat']
toolCallTimeoutMs: 60000
failOnStartupError: false
# reconnect: { enabled: true, initialDelayMs: 500, maxDelayMs: 30000, maxAttempts: 10 }
- id: mcp-remote-docs
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: docs
transport: streamable-http
url: http://127.0.0.1:3333/mcp
headers:
Authorization: !!js '`Bearer ${process.env.MCP_TOKEN ?? ""}`'
两点安全提醒:MCP 服务器进程在 agent 沙箱之外运行(它自带权限);env 里不要写明文密钥,用 !!js process.env.X 求值。
改完任何一层配置,按这个顺序确认:
--dump-config 里能看到你的值(不是你以为生效了)。id(拼错只有警告)。settings.yaml 的取值是合法枚举(例如 defaultPreset 必须是存在的 preset id)。tools、fs-sandbox、sandbox-policy 这类基础设施行改成互相打架的状态(例如同时挂 fs-local 和 fs-sandbox,profile 会拒绝加载)。是:一段按需加载的指令。发现阶段只读 frontmatter(便宜),加载阶段把正文原样交给模型。
不是:不是插件。skill 不能注册工具、不能声明依赖、不能限制工具、不能执行代码。DSH 的 frontmatter 里没有 allowed-tools、没有 model、没有 metadata.requires.bins。
你可能会在别人安装的 skill(比如百炼系列的
SKILL.md)里看到metadata.requires: bins: ["bl"]。那是那个工具自己的约定,DSH 只是把metadata原样存下来并不解释它——既不会检查bl是否存在,也不会因此报错。别把第三方约定当 DSH 规范。
| 键 | 必填 | 类型 | 规则 |
|---|---|---|---|
name |
✅ | string | 非空,且必须匹配 ^[a-z0-9]+(?:-[a-z0-9]+)*$(kebab-case)。目录名不参与校验,但建议一致 |
description |
✅ | string | 非空。这是目录里唯一被模型看到的字段 |
whenToUse |
❌ | string | 会被解析和保存,但不会渲染给模型 |
metadata |
❌ | mapping | 原样保存,DSH 不解释 |
disable-model-invocation |
❌ | bool | true = 模型看不到、也不能加载 |
user-invocable |
❌ | bool | false = 用户不能用 /名字 触发 |
布尔值接受 true/false、yes/no、on/off、1/0(大小写不敏感)。历史写法 disableModelInvocation / modelInvocable / userInvocable 会被明确拒绝并提示改用连字符写法。
最小可用 skill:
---
name: hello-world
description: Print a friendly greeting. Use when asked for a hello world example.
---
Say "Hello, world!" in one line. Do not add commentary.
| 优先级 | 来源 | 路径 |
|---|---|---|
| 100 | project-dsh | <项目根>/.dsh/skills |
| 200 | project-agents | <项目根>/.agents/skills |
| 300 | custom | 插件配置的 customSkillDirs |
| 400 | user-dsh | ~/.dsh/skills |
| 500 | user-agents | ~/.agents/skills |
| 600 | bundled | 随包提供 |
.git 的目录;找不到就用当前目录。<name>/SKILL.md 和扁平的 <name>.md。**/SKILL.md 递归发现不支持——放进 skills/foo/bar/SKILL.md 是找不到的。~/.dsh/skills 会跳过它的 .system 子目录。实践建议:团队规范进仓库(rank 100–200),个人口味进 ~/.dsh/skills(rank 400)。这样克隆仓库的人自动获得团队 skill,而不会被你的个人习惯覆盖。
这是最关键的一节,理解它你就能预判"为什么这个 skill 没被用上"。
第一步:目录。skill 的目录不在系统提示词里。它是在会话的某个 pre-step 阶段,以一条持久的 user 角色 <system-reminder> 消息注入的,内容只有:
<available_skills>
- `run-tests`: Run this repository's test suite and turn failures into ...
- `project-standards`: Enforce this repository's coding standards ...
</available_skills>
第二步:截断。description 会先做空白归一化,再截断到 500 字符(配置项 catalogDescriptionMaxLength,默认 500,最小 3),超长补字面量 ...。
第三步:正文。模型调用 skill({name})(或用户输入 /name)时,正文才被读盘,并且每次都重读(没有缓存)。返回结构:
<skill_content name="run-tests">
<skill_resources> ... 相对路径按 skill 目录解析 ... </skill_resources>
<skill_instructions> ...正文... </skill_instructions>
两个直接推论:
description 的前 500 字符是你唯一的"索引"。触发词必须出现在前面。reference/。这是最容易让人抓狂的一点。以下任意一种情况,提供方只打一条警告日志,然后整条 skill 消失,模型完全看不出区别(就像这个 skill 不存在):
--- 没闭合;name / description 缺失、不是字符串或为空;name 不是 kebab-case(例如 Run_Tests);disableModelInvocation)或调用策略字段不是布尔。自检命令(会话里)——让 agent 直接读文件并逐条核对规则,比"我改了应该好了"可靠:
读 <path>/SKILL.md,检查:① frontmatter 是否以 --- 开头并以 --- 结束;
② name 是否匹配 ^[a-z0-9]+(?:-[a-z0-9]+)*$;③ name 与目录名是否一致;
④ description 是否非空且前 500 字符内包含触发词;⑤ 是否用了 disableModelInvocation 这类旧键。
只报结论和证据。
三段式,越靠前越重要:
[做什么:动词开头的具体能力]。[什么时候用:触发场景 + 触发名词]。[可选:反触发 - 什么情况别用]。
三个档次对比:
# 差:全是形容词,没有触发词
description: 帮助团队写更好的代码。
# 中:有场景,但触发词太晚,且没有边界
description: 本 skill 提供一套经过实践检验的、覆盖多个方面的、系统化的代码质量保障方案,包括但不限于命名、注释、错误处理、日志、依赖管理、测试、文档等多个维度,适用于各种规模的团队和项目……
# 好:能力 + 触发名词 + 反触发
description: >-
Run this repository's test suite and turn failures into a classified, evidence-backed report:
reproduction command, failing assertion, first divergence, suspected owner package, and a
recommended next action. Use when asked to run tests, check whether a change broke anything,
verify a fix, or explain why CI is failing.
# <标题>
<可直接复制执行的命令>
| 项 | 通过 | 不通过怎么办 |
<要跑什么命令、要贴什么输出>
四条硬经验:
- **命令要能直接执行**。写 `pnpm --filter <package> test`,不要写"运行相关测试"。
- **给判定标准,不给形容词**。"优雅"不是标准,"公共 API 未删除,或已列出全部调用方"才是。
- **给停止条件**。没有停止条件的 skill 会变成死循环制造机:`同一失败修 3 次仍不通过 → 停止修改,把现象+3 次改动+当前假设交给用户`。
- **写反触发**。"普通问答、写作、翻译不走本 skill"能让模型少犯一半的误触发。
### 2.6.3 渐进式披露
约定俗成的目录(只有 `SKILL.md` 是被代码识别的,其他纯属约定):
```text
my-skill/
SKILL.md # 路由 + 判定 + 短步骤
reference/ 长清单、参数表、错误码映射
assets/ 模板、图片、示例数据
templates/ 产出物骨架
scripts/ 可直接调用的脚本
原则:SKILL.md 只放"每次都要用的判断",细节放 reference/,并在正文里用相对链接指过去——加载结果里已经告诉模型"相对路径按 skill 目录解析,按需加载引用文件"。两个真实例子:examples/skills/project-standards/SKILL.md(规范类)、examples/skills/run-tests/SKILL.md(流程类)。
| 该写 | 不该写 |
|---|---|
| 会重复发生的多步流程(跑测试、发布、迁移) | 一次性任务 |
| 判定标准容易被忘记(验收口径、禁止项) | 已经被 AGENTS.md 覆盖的通用规则 |
| 需要精确命令与参数的知识 | 模型本来就会的常识("要写测试") |
| 容易被误触发的能力需要写清边界 | 应该写成代码/脚本的确定性逻辑 |
判断法:如果这段话你第二次对 agent 说,就该进 skill 或
AGENTS.md。如果只是这一次的特殊要求,直接写在 prompt 里。
用 run-tests skill 检查当前改动——比"帮我看看测试"更稳。/<skill 名>(只对 user-invocable 不为 false 的 skill 有效;这也是 disable-model-invocation: true 的 skill 唯一的入口)。user-invocable: false 只是不让用户触发;要让模型也看不到,用 disable-model-invocation: true。想整体换一套 skill 根目录,用 preset 的 skill-filesystem + customSkillDirs。① 权限层 沙箱模式 + 审批策略 ← 能写哪、要不要问你
② 能力层 挂哪些工具插件行 ← 它连"想调用"都不存在
③ 指令层 AGENTS.md / persona / plan mode ← 它知道你希望它怎么做
排障时先定位是哪一层出的问题:
| 症状 | 大概率是哪一层 |
|---|---|
报 [sandbox: file access denied under ... mode] |
① |
报 the user rejected tool "..." / the user rejected escalating ... |
①(审批) |
报 工具不存在 / 模型说没有某个工具 |
② |
| agent 改了你不想让它改的地方,但没有任何报错 | ③(缺约束)或 ①(档位太松) |
关键认知:三层里只有 ① 是强制执行的。③ 是文字约束——它能大幅降低概率,但不能阻止行为。所以"重要禁令"要么进 ①(沙箱、审批),要么进 ②(不挂那个工具),别只写在 AGENTS.md 里。
一个预设 = 一个沙箱模式 + 一个审批策略的组合。
| 沙箱模式 | 写入范围 | 说明 |
|---|---|---|
read-only |
不授予可写根(除 /dev/null 这类必需出口) |
只读分析、评审、答疑 |
workspace-write |
工作区根 + 后端定义的临时区域 | 默认档;日常开发 |
danger-full-access |
不隔离,按原始方式启动 | 不设防 |
| 审批策略 | 行为 |
|---|---|
ask |
需要决策时向用户发起审批;没有应答者时默认拒绝(fail closed) |
never |
服务内部在分发之前就返回拒绝,后续监听器无法绕过 |
随附预设表:read-only(read-only + ask)、workspace-write(workspace-write + ask)、danger-full-access(danger-full-access + never)。
自定义预设(完整示例见 examples/profile-patch.example.yml 第 1 段):
- id: permission
name: '@deepseek-ai/dsh-permission-presets'
config:
presets:
audit-only:
sandbox: read-only
approval: ask
name: 只读审计
description: 自由读取与搜索;任何写入都要人工确认。
safe-write:
sandbox: workspace-write
approval: ask
trusted:
sandbox: danger-full-access
approval: never
defaultPreset: safe-write
三条规则:
custom 是保留名:它表示"当前旋钮组合不匹配任何预设"的派生状态,你无法选中或持久化它,也不能用它命名一个预设。defaultPreset 只在新会话创建时读一次;改配置不影响正在跑的会话。/permission(不带参数显示当前档位与可选表,带参数切换)。Windows 上的真实语义(很容易踩):workspace-write 授予的是「工作区 + 每个会话私有的随机临时目录」,环境变量 TMP/TEMP 会被改写到那个目录,系统的临时根目录不会被隐式授予。执行完整性是 "partial":NTFS 硬链接可以让同一个文件对象被另一个路径访问。另外,被限制的子进程不能用管道 stdio(spawn(..., {stdio:'pipe'}) 会 EPERM)——这是沙箱边界的一部分,不是 bug。
拒绝与升级:被拦时你会看到
[sandbox: file access denied under <mode> mode]
[sandbox: escalation available — retry this exact <subject> once with sandbox_permissions ... + justification; the approval prompt asks the user]
升级阶梯是严格向上:read-only → workspace-write|danger-full-access,workspace-write → danger-full-access;sandbox_permissions 与 justification 必须成对出现。注意本会话的一个反例:当前会话的权限档是 danger-full-access(审批 never),运行时上下文明确说"需要审批的动作会被自动拒绝,不要请求升级"。也就是说在这种档位下,"升级请求"这条路是关掉的——这是刻意的:要么你信任它,要么别给它这个档位。
网络不在沙箱词汇表里。沙箱只管文件效应;HTTP_PROXY/HTTPS_PROXY/NO_PROXY(或 .dsh/.env)影响的是 DSH 自己发出的请求(模型、搜索、抓取、MCP)。
默认挂载了 fs-observation-policy,它把文件工具变成"先看后改":
edit 目标必须先被 read 过,否则 FS_NOT_OBSERVED;write 覆盖已存在的文件也必须先 read;write 可以走受保护的创建流程;FS_STALE_VERSION,提示你重读再试。两个必须知道的后果:
edit 很可能报"没读过"——这是设计,不是故障。正确反应是重读那个文件。plan mode 的机制值得说清楚:它不做任何工具拦截,只注册一段系统提示词规则和一个 exit_plan_mode 工具,靠约束模型的行为来生效。段落的措辞很硬("Do not edit or write files … commit"、"用户的口头同意不构成批准")。
用法:
/plan 给这个仓库加一个导出 CSV 的接口
agent 会先探索、再规划,最后调用 exit_plan_mode 提交计划。规则细节:
plan 参数必须是以 # 开头的 markdown(正则要求 # 后有非空白字符)。Plan approved — plan mode exited; carry out the plan starting with your next step.approval: never 不会绕过它。exit_plan_mode 在两种模式下都注册(保持工具目录稳定),非计划模式下调用会报 only available in plan mode。实践建议:把"改三个文件以上 / 触碰公共 API / 改数据库结构 / 重构"这几类任务一律先过 plan mode。代价是一轮对话,收益是你能在改动发生前看到它的理解对不对。
~/.dsh/AGENTS.md (用户全局,跨仓库)
→ <项目根>/AGENTS.md (项目根 = 最近的 .git 祖先目录)
→ <项目根>/子目录/AGENTS.md (越具体越优先)
AGENTS.md、CLAUDE.md;叠加层是 AGENTS.local.md、CLAUDE.local.md(放在 AGENTS.md 之后)。CLAUDE.md 软链到 AGENTS.md 不会重复注入。maxBytes 决定,基础 bundle 设的是 65536 字节;单个源文件默认上限 1 MiB。超预算时先整份丢弃更宽的文件,再截断最具体的那个,并留下 Workspace instruction budget ... 提示。read / write / edit 触碰到那个目录,下一次请求才会补注入;bash 里 cd 不会触发(shell 的目录状态不是可靠的观察点)。需要子目录规则时,明确让 agent 读一遍那个文件。AGENTS.md,要等下次文件操作或会话恢复才对上。把 AGENTS.md 当入职文档 + 检查表,而不是愿景文档。参考 examples/AGENTS.example.md,它的骨架是:
① 这是什么仓库 (一句话定位 + 命令)
② 常用命令 (只列真实能跑的,带注释)
③ 目录约定 (放什么 / 不放什么)
④ 编码要求 (能判定的规则,不是形容词)
⑤ 测试与验收标准 (必须给出哪些命令的实际输出)
⑥ 硬性禁止 (不改历史迁移、不提交密钥、不 push …)
⑦ 工作方式 (先计划、小步、并行时写清边界)
写 AGENTS.md 的四条纪律:
catch 必须注释说明吞掉了什么"有用。system-prompt 行的 personaPrefix(基础 bundle 是空串)。dsh-persona 覆盖它:prefix(必填)、suffix、complete(true 时 prefix 成为整个系统提示词,其他段落都进不来)、includeRuntimeContext(false 时连沙箱/审批的运行时上下文也一起压掉)。示例见 examples/agent-presets/readonly-agent/agent.cordis.yml。| 限制 | 值 / 行为 | 出处 |
|---|---|---|
| 委派深度 | maxDepth 默认 3;可设 0(禁止)或 'provider-managed';本机实测上限为 1 |
源码 + 实测报错 subagent depth 2 exceeds maxDepth 1 |
| 超限表现 | 工具仍然可见,调用时返回错误结果 | 源码 |
| 子 agent 的审批 | 委派时父会话的沙箱覆盖被复制,审批一律钉为 never,子 agent 无法自行请求升级 |
源码 |
| 子 agent 工具屏蔽 | toolFilter(allow/deny)作用于子 agent;被屏蔽的工具从子 agent 提示词里消失且拒绝执行;写了不存在的名字启动即失败 |
源码 |
| preset 收窄工具 | 没有声明式 allow/deny 字段;靠"挂哪些插件行"来体现 | 源码 |
| 工具注册表的全局配置 | 只有 mode 与 maxParallelSubCalls;没有全局 allow/deny |
源码 |
| plan mode | 纯文字约束,不拦截工具 | 源码 |
| scope | 明确不是安全边界(它管可见性/归属,不管权限) | 源码 |
| workflow 脚本 | 无文件、网络、定时器、require/process;只有 6 个全局;vm 是"容器而非安全边界" |
源码 |
| workflow schema | 只支持 type/properties/required/additionalProperties/items/enum/const/oneOf(oneOf 还要 ≥2 分支且不能有同级关键字);pattern/format/数值上下界都不行 |
源码 |
| 任务 | 预设 | 子 agent 深度 | plan mode | AGENTS.md 重点 |
|---|---|---|---|---|
| 只读分析/评审 | audit-only(read-only+ask) |
1(叶子) | 不需要 | 报告格式与证据要求 |
| 日常改代码 | workspace-write |
2 | 建议先过 | 命令、目录约定、禁止项 |
| 大重构 | workspace-write |
2 | 必须 | 分阶段验收标准 |
| 批量迁移 | workspace-write |
用 workflow 编排 | 必须 | 每阶段证据与回滚方式 |
| 可信仓库里的危险操作 | trusted(danger-full-access+never) |
1 | 建议 | 明确的"不要做"清单 |
一个健康的工作单元必须四要素齐备,缺一个就还得拆:
| 要素 | 问自己 | 反例 |
|---|---|---|
| 输入 | 起点是什么(文件、接口、数据、commit)? | "把系统优化一下" |
| 输出 | 交付物是什么(能打开的文件、能跑的命令、能看到的页面)? | "更好的代码结构" |
| 证据 | 怎么证明做对了(命令 + 输出、测试名、截图路径)? | "看起来没问题" |
| 边界 | 哪些文件/接口不动?和谁并行时会冲突? | 两个 agent 同时改同一个文件 |
四要素里最容易被跳过的是证据。一旦你要求"证据是命令加输出",拆分质量会自动提升——因为说不清证据的任务,往往本身就是没想清楚的。
| 手法 | 什么时候用 | 例 |
|---|---|---|
| 按验收标准拆 | 一个任务有多个互相独立的"通过条件" | "接口能返回 CSV" 与 "超过 10 万行时分页" |
| 按子系统拆 | 改动落在不同目录/包 | 前端表单 / 后端接口 / 迁移脚本 |
| 按阶段拆 | 需要先有证据再动手 | ①复现 ②定位 ③修复 ④回归 |
| 按不确定性拆 | 有未知的技术前提 | 先做 spike 验证方案可行性,再正式实现 |
| 按可验证切片拆 | 改动可以逐步放出 | 先读路径 → 再写路径 → 再错误处理 |
| 按并行度拆 | 各部分无共享可写资源 | 三个目录的同类改造 |
| 按风险拆 | 高风险动作要单独隔离 | 数据迁移单独一步,先备份后执行 |
这个任务……
├─ 一轮对话能做完,且不需要我确认方案?
│ └─ 直接做:先读现状 → 最小改动 → 立刻验证
├─ 能做完,但我希望先看方案?
│ └─ /plan → exit_plan_mode(第 3 章)
├─ 跨多轮、有明确"什么时候算完成"?
│ ├─ 需要自动续跑 → create_goal + update_goal(有轮次上限)
│ └─ 只需要可见进度 → todo_write
├─ 有 2–5 个互相独立的方向?
│ ├─ 子任务不需要父会话历史 → subagent(后台)
│ └─ 子任务需要理解已完成的前文 → subagent_fork
└─ 同构对象几十个以上(文件/目录/模块)?
└─ workflow 脚本扇出(注意 1000 个 agent 的硬上限)
todo_write:当前执行清单status 只有 pending / in_progress / completed;没有 id、优先级、备注字段。in_progress 可以同时有多个(配置 allowParallelInProgress: true)——但只在真的并行时这么做(后台命令、并发子 agent)。create_goal / update_goal:跨轮目标create_goal {objective, max_goal_rounds?},默认轮次上限 256。update_goal {goal_id, revision, action, objective?, max_goal_rounds?, blocked_reason?},action ∈ edit|pause|resume|complete|blocked。blocked 有个硬门槛:必须已经连续跑了至少 3 轮同一个阻塞条件才允许标记,否则会被拒绝(blocked requires at least 3 consecutive goal rounds)。这是防止你拿"blocked"当"我不想做了"的挡箭牌。edit / pause / resume 需要人的直接授权;complete / blocked 在目标轮次内也可以由 agent 提出。goal "<id>" exhausted N goal rounds; increase maxGoalRounds before resuming。goal 与 todo 的分工:goal 回答"什么时候算完成",todo 回答"现在在做什么"。一个跨轮的 goal 通常会配一张不断更新的 todo 清单。
job_*:后台作业job_output {job_id, wait?, timeout_ms?} 收集,job_kill 停止,job_list 列出。running 且作业继续存活(不是失败)。job_kill 停掉不需要的。| 反例 | 为什么坏 | 改法 |
|---|---|---|
| 验收标准写成形容词 | 无法判定完成,agent 会不断"优化" | 换成命令 + 输出、测试名、可打开的文件 |
| 把两个改同一文件的子任务并行 | 互相覆盖,谁最后写谁赢 | 按文件所有权拆,或串行 |
| 探索和实现塞进同一批 | 探索结论没定型就动手,白改一轮 | 先一轮只做探索与计划 |
| 拆到无法验证的碎块 | 每块都没法跑,攒到最后一起炸 | 每一块都要有一个可执行的验证动作 |
| 委派提示词写"你看着办" | 子 agent 没有你的上下文,只能瞎猜 | 给输入、输出格式、证据要求、边界 |
| 任务描述里混入两个目标 | 做完一个就算"完成",另一个悄悄丢掉 | 一个单元一个目标,或显式列出子目标与验收 |
把每个子任务写成下面这块,粘进 subagent 的 prompt:
【目标】用一句话说清要产出什么。
【背景】子 agent 看不到我们的对话,所以这里必须自包含:
- 仓库根:<绝对路径>
- 相关文件:<路径 1>、<路径 2>
- 已知约束:<例如 不允许改公共 API、必须复用现有 logger>
【输入】你要读的东西(路径/命令/接口)。
【输出格式】固定格式,便于我汇总:
1. 结论(≤3 行)
2. 证据:文件:行号 或 命令 + 关键输出片段
3. 未确认项与原因
4. 建议下一步(一条)
【边界】
- 只读 <目录>,不要改任何文件。(或:只允许改 <文件>)
- 不要运行 <危险命令>。
- 不要委派给其它 agent(深度上限为 1,会直接失败)。
【失败处理】做不下去时,不要猜:报告阻塞点、已尝试的做法、需要什么信息。
| 工具 | 本质 | 上下文 | 默认等待 | 何时用 |
|---|---|---|---|---|
subagent |
新建独立子 agent(spawn) | 看不到你的对话 | run_in_background: false |
自包含的独立任务 |
subagent_fork |
从父会话派生(fork) | 继承父会话已完成轮次的前缀 | 默认后台(one-shot) | 需要理解前文的后续分析/评审 |
workflow |
一段 JS 编排脚本 | 脚本协调,子 agent 干活 | 前台阻塞 | 几十个同构对象的扇出 |
job_output / job_kill / job_list |
后台作业控制 | — | — | 收集/停止后台工作 |
send_message |
给可继续的子 agent 追加消息 | — | 立即返回投递确认 | 子任务需要跟进 |
interrupt_agent |
停止某个 agent 的当前轮 | — | 立即返回接受确认 | 方向错了,止损 |
list_agents |
列出我启动的 agent(children/descendants) |
— | — | 回忆"我开了哪些" |
subagent 的入参(模型可见的默认 schema):description(3–5 词,展示用)、prompt(必填,必须自包含)、run_in_background(默认 false)。开了模型选择后还会多出 provider / model / reasoning_effort,以及一个 list_subagent_models 工具用来查可用路由。
subagent_fork 刻意不允许选模型:provider/model 必须与父一致,这样继承下来的历史才能命中 KV cache。
工具层的固定指引(很值得直接遵守):
默认把
subagent放到后台跑;把互相独立的委派放在同一条消息里一起启动,然后在它们运行期间继续做有用的工作;只有当下一个动作依赖某个子 agent 的结果时,才设run_in_background: false。
场景:多角度研究、多目录审计、多方案对比。
我:把下面三块一起查(同一条消息里三个后台 subagent):
① apps/ 下的接口层 ② packages/ 下的领域层 ③ 迁移脚本与数据库约束
每个都按固定格式回报:结论 / 证据(文件:行号) / 未确认项
(主干继续读 AGENTS.md 与最近提交,不空等)
→ 三个结果到齐后,我做交叉核对与汇总,冲突处单独列出
纪律:固定输出格式是扇入的关键。没有统一格式,汇总阶段你会读到三段风格迥异的散文。
场景:迁移、逐文件改造、给一批模块补测试。
用 workflow 的 pipeline:每个 item 独立走完所有阶段,不需要等别人。
pipeline(files, 改造一个文件, 对它跑测试, 生成改动说明)
关键收益:某个文件失败只会让那一个 item 变 null,其余照常推进。真正的错误(选项写错、schema 不支持、超出上限)会直接杀掉整个脚本——这是刻意的"致命错误不许伪装成普通失败"。
场景:高风险改动、改核心逻辑、动数据库。
① 主 agent 实现(或生成者子 agent 实现)
② 另开一个审查者子 agent,只给它"改了什么"和"原始证据",不给实现者的自我总结
要求:独立复现、列出反例、指出无法证明的部分
③ 主 agent 处理分歧:能复现的当作真问题修,不能复现的写清原因
纪律:审查者必须看原始证据(diff、测试输出、日志),不是看实现者的总结。否则你只是在让第二个 agent 复述第一个 agent 的偏见。
场景:需求模糊。
先让一个子 agent 只做"把模糊需求变成可验收步骤",产出必须包含每条步骤的验证方式;方案定稿后再开执行。好处是探索阶段的噪音全部留在子 agent 的上下文里。
场景:长耗时命令(构建、全量测试)、外部依赖(拉数据、跑爬虫)。
我:在后台跑全量测试(run_in_background: true)
同时:读这份配置、写这次改动的说明文档
→ 收到完成通知后再收集结果
纪律:不要轮询。job_list 是"我还开了哪些"的记忆工具,不是等待工具。等待会浪费你的上下文;去做别的。
场景:上百个文件/模块的审计、批量改造、多角度研究。
见 5.3 的脚本契约。判据很简单:当你能用"对每个 x 做同一件事,最后汇总"描述任务时,就该用 workflow 而不是一个个委派。
meta(必填,纯数据):{ name(短 kebab-case)、description、whenToUse?、phases?: [{title, detail?, provider?, model?}] }。script(必填):纯 JavaScript 函数体。允许顶层 await;不要写 export const meta(会得到明确的 SCRIPT_PARSE 错误);结尾用 return <可 JSON 序列化的值>。args(可选):一个对象,脚本里通过同名全局读取;数组请包一层,例如 {"files": [...]}。| 全局 | 语义 |
|---|---|
agent(prompt, opts?) |
跑一个子 agent 到完成。没给 schema 时解析为子 agent 的最终文本 |
pipeline(items, ...stages) |
每个 item 独立过所有阶段,阶段之间没有屏障;stage 收到 (prev, item, index) |
parallel(thunks) |
并发跑一组零参函数并 Promise.all 等待(屏障) |
phase(title) |
开始一个进度阶段(只影响展示,不产生执行结构) |
log(message) |
记一条进度 |
args |
工具调用传进来的 args(对象副本) |
agent 的 opts 只接受这几个键:label、phase、schema、provider、model。写 effort / isolation / agentType 会明确报不支持;写其他未知键直接抛错。
| 情况 | 结果 |
|---|---|
子 agent 正常完成,无 schema |
子 agent 的最终文本 |
子 agent 正常完成,带 schema |
校验过的对象 |
子 agent 完成但没有返回 structured |
null(用 .filter(Boolean) 过滤) |
| 子 agent 非正常完成 | null |
parallel 里某个 thunk 抛错 |
那个位置是 null,其余照常 |
pipeline 里普通阶段抛错 |
该 item 变 null,跳过它剩余的阶段;其余 item 继续 |
| 钩子误用(参数错、未知 opt、schema 越界、触到上限、开始失败、取消) | 抛错并杀掉整个脚本 |
脚本里绝对没有的东西:文件、网络、定时器、require/process。脚本只负责协调,脏活全是子 agent 干。
| 项 | 值 |
|---|---|
| 并发子 agent | maxConcurrentAgents 默认 0 → 自动 min(16, max(1, 可用并行度 - 2)) |
| 单次运行子 agent 总数 | maxTotalAgents 默认 1000 |
| 单次调用元素数 | maxItemsPerCall 默认 4096 |
| 初始同步片段超时 | syncTimeoutMs 默认 5000 ms |
| 收尾宽限 | disposeGraceMs 默认 5000 ms,超时强制结算并终止 worker 线程 |
| 整体运行时长 | 没有总墙钟超时 |
| 结果渲染上限 | tool-workflow 的 maxResultChars 默认 50000 字符,超出截断 |
另外:workflow 是前台阻塞的,没有"后台启动 + 轮询"的 API;取消会丢弃部分结果并以错误返回。
phase('扫描')
log(`共 ${args.files.length} 个文件`)
const schema = {
type: 'object',
properties: {
findings: {
type: 'array',
items: {
type: 'object',
properties: {
severity: { type: 'string', enum: ['high', 'medium', 'low'] },
summary: { type: 'string' },
evidence: { type: 'string' },
},
required: ['severity', 'summary', 'evidence'],
additionalProperties: false,
},
},
},
required: ['findings'],
additionalProperties: false,
}
const results = await parallel(
args.files.map((file) => () =>
agent(
`只读审查文件 ${file}。\n` +
`只报能证明的问题:每条给出严重度、一句话结论、以及文件:行号或命令输出作为证据。\n` +
`没有发现就返回空数组,不要为了凑数编造。`,
{ label: `audit:${file}`, phase: '扫描', schema },
),
),
)
// 失败的子 agent 是 null,必须过滤
const findings = results
.filter(Boolean)
.flatMap((r) => r.findings ?? [])
phase('汇总')
const bySeverity = findings.reduce((acc, f) => {
acc[f.severity] = (acc[f.severity] ?? 0) + 1
return acc
}, {})
return {
files: args.files.length,
failed: results.filter((r) => r === null).length,
bySeverity,
findings,
}
phase('迁移')
const fileSchema = {
type: 'object',
properties: {
changed: { type: 'boolean' },
files: { type: 'array', items: { type: 'string' } },
testCommand: { type: 'string' },
testResult: { type: 'string' },
},
required: ['changed', 'files', 'testCommand', 'testResult'],
additionalProperties: false,
}
const outcome = await pipeline(
args.modules,
// 阶段 1:改造
(prev, mod) =>
agent(`把模块 ${mod.path} 从旧的 API 用法迁移到新 API。只改这个模块目录内的文件;\n` +
`完成后给出:改了哪些文件、你实际跑的测试命令、以及该命令的真实输出片段。\n` +
`不要试图委派给其它 agent。`, { label: `migrate:${mod.name}`, phase: '迁移', schema: fileSchema }),
// 阶段 2:独立复验(拿阶段 1 的结果当输入,但要求自己重跑)
(prev, mod) =>
agent(`独立复验模块 ${mod.name} 的迁移结果。\n` +
`上一阶段声称:${JSON.stringify(prev)}\n` +
`请自己重跑那个测试命令,报告真实结果;如果与声称不符,直接说明差异。`,
{ label: `verify:${mod.name}`, phase: '复验' }),
)
const ok = outcome.filter((o) => o !== null)
return {
total: args.modules.length,
completed: ok.length,
dropped: args.modules.length - ok.length,
details: outcome,
}
调用示例(args 里包一层,保持 wire schema 诚实):
{ "modules": [ { "name": "billing", "path": "packages/billing" }, { "name": "auth", "path": "packages/auth" } ] }
两步:
settings.yaml 里放开允许的路由:subagent-model-selection:
enabled: true
allowedModels:
- provider: deepseek-official
model: deepseek-flash
- provider: deepseek-official
model: deepseek-v4-flash
provider + model(只给一个会被拒绝),并且必须命中上面的某一条,否则报 child LLM route "<p>/<m>" is not allowed for this Session。注意:enabled: true 时 allowedModels 不能为空,否则加载即报错。这个策略在每次新建顶层会话时采样一次,之后由子会话继承,事后改设置不影响已创建的会话。fork 出来的子 agent 不接受路由选择(为了 KV cache)。另外,ACP / Codex / Claude Code 这类外部提供方不支持这些 per-call 选项。
job_kill 收掉。job_output 等待上限 600 秒;超时只是"还没好",不是失败。list_agents 没有分页也没有上限,长时间、多子 agent 的父会话每次调用都会全量列举——它是"回忆工具",不是轮询工具。| 反模式 | 后果 | 正确做法 |
|---|---|---|
需要父上下文的活儿交给 subagent |
子 agent 只能瞎猜,返回无用结果 | 用 subagent_fork,或把上下文写进 prompt |
| 让子 agent 再委派 | 深度超限,报 exceeds maxDepth 白跑一轮 |
两级结构交给 workflow 编排 |
用 list_agents/sleep 等结果 |
浪费上下文与时间 | 依赖完成通知;期间做独立工作 |
| 两个子 agent 写同一批文件 | 互相覆盖 | 按文件所有权拆,或串行 |
| 子 agent 只回"已完成" | 无法验收,等于没做 | 固定输出格式 + 要求原始证据 |
| 把 workflow 当"免费并行" | 撞 1000 agent / 4096 items 上限,前台长时间阻塞 | 先估规模,必要时分批 |
一个多 agent 任务收尾前,逐条过:
null 的数量、dropped 计数),没有被静默吞掉。AGENTS.md 或 skill。会话越长越"笨",通常不是模型退化了,是上下文被噪音占满了。DSH 里有四个机制在管这件事,理解它们的分工比记住参数重要。
| 机制 | 管什么 | 触发时机 | 关键参数 |
|---|---|---|---|
compaction-basic |
整段历史的压缩 | 达到窗口比例阈值时(也可 /compact 手动) |
thresholdRatio 0.8、retainRatio 0.16、auto true |
tool-result-pruner |
单条过大的工具结果 | 先于对话压缩发生 | thresholdChars 8192、headChars 4096、tailChars 1024 |
spill-policy |
超大工具结果转存文件 | 就地替换成头尾预览 + 文件定位符 | maxInlineBytes 50000(省略即关闭) |
repeat-tool-reminder |
原地打转的循环 | 同一工具同一参数重复 N 次 | thresholds [3,5,8] |
注意 thresholdRatio 是按路由后的上下文窗口乘比例算的;retainTokens 与 retainRatio 互斥,且必须小于缩略阈值(写错会在加载时直接失败,不会静默生效)。
/compact。这样新阶段带着"结论"而不是"过程"开始。/compact 再继续。glob 找路径、grep 找符号,只读命中的那几段。禁止"整个目录都读一遍"。read 本身有 readLimit 2000 行 / readMaxBytes 51200 的上限,超过就分页——别指望一次读完长文件。grep 定位关键行。spill-policy 会自动做一部分,但源头上更省。/compact。阶段收尾压缩一次,比一路膨胀到最后被强制压缩质量高得多。# 差:让主 agent 自己读 40 个文件
"看看 apps/ 下所有 Controller 有没有漏掉权限校验,把发现列出来"
→ 主上下文塞进 40 个文件的片段,后面几轮基本没法正常思考
# 好:用 workflow 扇出,只回收结论
phase('扫描') → 并行审查 40 个文件 → 每个返回 {file, missing, evidence}
→ 主上下文只拿到一份紧凑的结构化清单
判据:"读得多、结论少"的任务,一定要委派或脚本化。
需求:给现有仓库加一个"导出订单 CSV"的接口,并补测试,最后给一份验收报告。
不要直接说"加个导出接口"。先写清:
目标:GET /api/orders/export 返回 CSV
验收:
① 200 + Content-Type: text/csv; charset=utf-8,含 BOM(Excel 直接打开不乱码)
② 表头与字段顺序固定:orderId, createdAt, customer, amount, status
③ 超过 50000 行时按分页流式返回,不一次性加载进内存
④ 未登录返回 401;无权限返回 403
⑤ 新增单元测试覆盖 ① ③ ④,并给出实际运行输出
边界:不改公共 API 的导出签名;不改数据库结构;不动前端
这一步的价值:后面所有 agent(包括子 agent)都拿这份标准对准自己。
/plan 按上面的验收标准,制定实现计划
要看计划里有没有这几样:具体文件路径、每步的验证方式、对 ③ 的技术选择(游标 vs offset 流式)。没有验证方式的计划退回重写。
批准后,把计划固化:
create_goal objective: "实现订单 CSV 导出接口并补齐测试,验收标准见 ..."
todo_write: 5 条(路由/权限 → 查询流式 → CSV 序列化 → 测试 → 验收报告)
这两件事互不依赖、不共享文件:
我:同时开两个后台子任务
① subagent:梳理 <repo> 现有的导出类接口实现(路径、公共工具、错误码约定),
按固定格式回报:可复用的工具 / 现有约定 / 反例
② subagent:读 orders 相关的查询层,给出"如何流式读取而不全量加载"的三种候选做法,
每种给出文件:行号证据与风险
期间我自己读 AGENTS.md 与最近相关提交
→ 两者到齐后,我决定技术路线,再动手改代码
todo 1/5 in_progress:加路由与权限校验
→ 改完立刻跑该包的类型检查与该路由的现有测试
todo 1/5 completed
todo 2/5 in_progress:查询层流式读取
→ 跑一个针对性的单测(构造 6 万行数据,断言拿到的是迭代器而非数组)
...
纪律:一次只做一条 todo,做完立刻验证,不在脑子里攒批。
实现完成后,开一个只读评审,并把原始证据交给它,而不是你的总结:
我:subagent_fork(它能看到已完成的前文)
prompt:
【目标】独立复验这次 CSV 导出改动,找反例。
【你只能读,不要改文件】
【必须做的三件事】
① 打开 diff 涉及的文件,逐条对照验收标准 ①②③④
② 自己运行新增的测试,贴出真实命令与输出
③ 针对 ③ 的分页边界(49999/50000/50001 行)给出推理或反例
【输出】按 严重度 / 结论 / 证据(文件:行号 或 命令输出) 三列列出;无法确认的写清缺什么证据
【不允许】只复述实现说明
(想更严格一点:把 preset 换成 examples/agent-presets/readonly-agent/,它连 write/edit 工具都没有。)
update_goal action: complete。验收标准 状态 证据
① CSV 头/BOM 通过 命令 + 响应头片段
② 字段顺序 通过 测试名 + 断言输出
③ 分页边界 通过 49999/50000/50001 三个用例输出
④ 401/403 通过 测试名 + 输出
⑤ 测试覆盖 通过 pnpm --filter orders test 输出
未验证项 —
| 这次学到的 | 沉淀到哪 |
|---|---|
| 这个仓库的导出接口约定 | <repo>/AGENTS.md 的「目录约定」 |
| "怎么跑 orders 包的测试与边界用例" | skill run-tests(或新增 orders-tests) |
| "导出类接口必须带 BOM 且流式" 的判定 | skill project-standards |
| 评审时必须看原始证据这条纪律 | AGENTS.md 的「工作方式」 |
| 常用但啰嗦的权限/超时配置 | ~/.dsh/profiles/web/cordis.patch.yml |
一次任务的终点不是"做完了",而是"下次不用再解释一遍"。 这是 vibe coding 里复利最高的动作。
一个改动只有在下面四项都满足时才算完成:
skip、没有 --passWithNoTests、没有调低覆盖率阈值。| 症状 | 常见根因 | 处理 |
|---|---|---|
| 我写的 skill 不生效,模型像不知道它 | frontmatter 问题导致静默丢弃:没 ---、YAML 非法、name 不是 kebab-case、旧键名 |
按 第 2.5 节的自检清单逐条核对;注意只有日志里有警告 |
| skill 加载了但没被用上 | description 前 500 字符里没有触发名词 |
把"fmt 触发场景"前置,加反触发 |
子 agent 报 subagent depth 2 exceeds maxDepth 1 |
委托深度上限(本机为 1,默认 3);且工具仍然可见 | 不要设计递归委派;两级结构用 workflow 编排,或调大 tool-subagent 的 maxDepth |
| 子 agent 的回答完全跑偏 | subagent 看不到父会话 |
用 subagent_fork,或把输入/约束/输出格式写进 prompt |
cannot modify "<path>": file has not been read |
读后写策略:edit/覆盖已存在文件前必须先 read;会话恢复后观察态不持久 |
先 read 那个文件再改(这是设计,不是故障) |
FS_STALE_VERSION |
读之后文件被外部改动 | 重读一次再改 |
[sandbox: file access denied under <mode> mode] |
目标在工作区与私有临时目录之外 | 切到更合适的权限档,或改到工作区内;不要试图绕开 |
| 升级请求被直接拒绝 | 当前档位是 danger-full-access + 审批 never(或审批被禁用) |
这是刻意的;要么信任当前档位,要么先切到 workspace-write |
| 改了 profile 补丁但"没变化" | ① id 拼错只有警告;② 该行 config 被整体替换,你少写的键回到了默认值;③ 增删插件包需要重启 |
--dump-config 对比;抄整块 config 再改 |
| 补丁文件写好后启动失败 | 顶层不是数组,或文件只有注释(没有 []) |
空补丁写 [] |
settings.yaml 改了没效果 |
只影响新建会话;或取值不是合法枚举 | 新开一个会话;defaultPreset 必须是存在的 preset id |
| 后台作业起不来 | owner 并发上限(默认 10) | job_kill 掉不再需要的 |
| 会话越跑越糊涂 | 上下文被噪音塞满 | 第 6 章:先搜后读、大输出落盘、委派消化、及时 /compact |
| 安全相关:agent 改了不想让它改的东西 | 只写了 AGENTS.md(③ 层,纯文字) |
把约束下沉到 ①(沙箱/审批)或 ②(不挂那个工具) |
| MCP 工具没出现 | serverName 不符合 [A-Za-z0-9_-]{1,32}、transport 配置项写错、failOnStartupError 为 false 时启动失败被吞 |
先改成 failOnStartupError: true 让失败显形,再排查 command/url |
$DSH = 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd'
# 1. 我现在的配置到底长什么样
& $DSH --profile web --dump-config
# 2. 我的改动相对于官方默认值是什么
& $DSH --profile web --dump-default-config
# 3. 我的实验补丁能不能被接受(不动物理配置文件)
& $DSH --profile web --patch .\extra.yml --dump-config
subagent_fork;不需要 → subagent。run_in_background: true),然后去做别的。grep/glob 定位。/compact。| 项 | 怎么自测 |
|---|---|
本机 maxDepth 为什么是 1 而不是默认 3 |
在 --dump-config 里查 tool-subagent 行的 maxDepth,以及 preset 里是否有覆盖 |
| hooks(Claude Code / Codex)的完整事件语义与边界条件 | 挂一行 dsh-hooks-claude-code + 一个 hooks.json,在各事件里 echo 一行看是否触发;事件名见第 1.6 节的表 |
metadata 字段的实际用途 |
源码里只做原样保存;可写一个带 metadata 的 skill,观察是否影响任何行为(预期:不影响) |
skill-filesystem 的 customSkillDirs 等细节键 |
在 profile 补丁里设 customSkillDirs: [...],验证该目录下的 skill 是否出现在会话目录中 |
| todo 与 goal 之间是否有状态机关系 | 建一个 goal、写一份 todo,观察 goal 轮次是否读取 todo 状态(预期:两者独立) |
子 agent 会话是否加载 AGENTS.md |
让一个子 agent 回答"你看到了哪些工作区指令",与主 agent 的回答对比 |
这套东西里,最容易做、回报最高的三件事:
AGENTS.md(第 3.5 节)。配置和 preset 是锦上添花;上面这三件才是日常体感的主要来源。
十条 5 分钟能做完的配方。每条都给出改哪里 → 具体内容 → 怎么验证生效。
变量约定:
$DSH = DSH 启动器,当前机器上是
D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd(PowerShell 里用 & $DSH ...)~/.dsh = C:\Users\<你>\.dsh<repo> = 你的仓库根目录(含 .git 的那一层)场景:第一次在这个仓库里干活,agent 不知道为什么跑测试的命令是这个。
# 1) 复制模板到仓库根
Copy-Item 'E:\dsh\chat\vibe-coding-guide\examples\AGENTS.example.md' '<repo>\AGENTS.md'
# 2) 建团队 skill 目录
New-Item -ItemType Directory -Force '<repo>\.dsh\skills' | Out-Null
Copy-Item -Recurse 'E:\dsh\chat\vibe-coding-guide\examples\skills\run-tests' '<repo>\.dsh\skills\run-tests'
然后编辑 AGENTS.md:把「常用命令」「目录约定」「硬性禁止」三节换成这个仓库的真实内容(只写你真跑过的命令)。
验证:新开一个会话,问它"这个仓库提交前要跑什么命令、在哪里跑",看它是否答对;再问"你怎么知道的",它应该引用 AGENTS.md。
场景:"部署流程"每次都靠你口述。
New-Item -ItemType Directory -Force '<repo>\.dsh\skills\deploy-staging' | Out-Null
新建 <repo>\.dsh\skills\deploy-staging\SKILL.md:
---
name: deploy-staging
description: >-
Deploy the current branch to the staging environment and verify the rollout:
preflight checks, deploy command, health probe, log tail, and rollback trigger.
Use when asked to deploy, release, push to staging, or verify a deployed build.
---
# 部署到 staging
## 何时使用
- 用户要求部署/发布到 staging,或验证刚部署的版本。
**不适用**:生产发布(走 `deploy-prod`,另有人工闸门)。
## 前置检查
1. `git status --short` 必须干净。
2. 当前分支已推送:`git rev-parse HEAD` 与远端一致。
## 步骤
1. 部署:`<真实命令>`
2. 健康检查:`<真实命令>`,期望输出 `<期望>`
3. 观察 60 秒日志:`<真实命令>`
## 判定标准
| 项 | 通过 |
|---|---|
| 健康检查 | 返回 200 且 body 含 `"status":"ok"` |
## 停止条件
- 健康检查连续 3 次失败 → 立即执行回滚 `<命令>`,然后把现象交给用户。
- 日志出现 `<错误关键字>` → 停止观察,直接回滚。
验证:新会话里输入 /deploy-staging,看它是否按步骤执行;再输入"帮我看看部署",看模型是否自己加载了这个 skill(如果没触发,说明 description 的触发词不够靠前)。
场景:默认档是 danger-full-access,你想日常用 workspace-write。
编辑 ~/.dsh/settings.yaml:
permission:
defaultPreset: workspace-write
验证:新开一个会话,/permission 应显示 workspace-write。注意已存在的会话不会变。
场景:在可信仓库里做大批量重构,反复审批太慢。
会话内直接切(只影响这个会话):
/permission danger-full-access
做完切回来:
/permission workspace-write
验证:/permission 不带参数显示当前档位与可选表。
场景:想一键切到"能读能搜、写操作必问"。
编辑 ~/.dsh/profiles/web/cordis.patch.yml(若为空文件,先写 [] 以外的一个数组):
- id: permission
name: '@deepseek-ai/dsh-permission-presets'
config:
presets:
audit-only:
sandbox: read-only
approval: ask
name: 只读审计
description: 自由读取与搜索;任何写入都要人工确认。
safe-write:
sandbox: workspace-write
approval: ask
trusted:
sandbox: danger-full-access
approval: never
defaultPreset: safe-write
注意 config 是整体替换:内置的 read-only / workspace-write / danger-full-access 三档不会自动保留(read-only 与 danger-full-access 的默认表里本来就有,这里为了让 defaultPreset 指得到 safe-write,把要用的档都列全)。
验证:
& $DSH --profile web --dump-config # 找到 permission 行,确认 presets 与 defaultPreset
新会话里 /permission 应能看到 只读审计 与 工作区写入。
场景:探索/汇总用便宜的模型,实现用强模型。
~/.dsh/settings.yaml:
subagent-model-selection:
enabled: true
allowedModels:
- provider: deepseek-official
model: deepseek-flash
- provider: deepseek-official
model: deepseek-v4-flash
委派时成对指定:
subagent(description: "扫描迁移脚本", prompt: "...", provider: "deepseek-official", model: "deepseek-v4-flash")
验证:先把 model 填一个没在 allowedModels 里的值,应该报 child LLM route ... is not allowed for this Session;说明策略生效了。
场景:想把 MCP 的文件系统/记忆服务接进来。
编辑 ~/.dsh/profiles/web/cordis.patch.yml:
- insert:
- id: mcp-filesystem
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: fs
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-filesystem', 'E:\dsh\chat']
toolCallTimeoutMs: 60000
failOnStartupError: true # 先设 true,让启动失败显形
验证:
& $DSH --profile web --dump-config | Select-String -Pattern 'serverName' -Context 1,3
重启 profile(插件包/插入行变化需要重启),然后在新会话里让它列出可用工具,应该看到 mcp__fs__*。确认没问题后再把 failOnStartupError 改回 false。
场景:评审时不放心 agent 手滑改文件。
New-Item -ItemType Directory -Force "$env:USERPROFILE\.dsh\.agent-presets" | Out-Null
Copy-Item -Recurse 'E:\dsh\chat\vibe-coding-guide\examples\agent-presets\readonly-agent' `
"$env:USERPROFILE\.dsh\.agent-presets\readonly-agent"
验证:新开一个空白会话,在 preset 选择器里应看到「只读评审」;让它"改一下某个文件",它应该告诉你 write/edit 工具不存在。
提醒:这个 preset 不挂
write/edit,但pwsh还在。要彻底只读,配合配方 5 的audit-only(read-only沙箱)使用。
场景:"审计 80 个文件里的错误处理"。
先让 agent 生成文件清单,再让它用 workflow:
用 workflow 工具审计下面这批文件(清单见 args.files):
每个文件一个子 agent,只读审查;必须用 schema 返回结构化结果:
{ findings: [{ severity: high|medium|low, summary, evidence }] }
evidence 必须是 文件:行号 或 命令输出。
最后汇总:按严重度计数、列出失败(null)的数量、把 high 的条目单独列出来。
并发会超过 16 时就分两批。
参考脚本:vibe-coding-guide.md 第 5.3 节。
验证:返回的 JSON 里 failed 字段应当明确列出丢掉的 item 数;findings 里每条都有 evidence。
场景:写了 skill,模型像没看见。
帮我排查这个 skill 为什么没被加载:<绝对路径>\SKILL.md
逐条检查并给证据:
① 文件是否以 --- 开头、并在正文前有闭合的 ---
② frontmatter 的 YAML 能否解析
③ name 是否匹配 ^[a-z0-9]+(?:-[a-z0-9]+)*$,且与目录名一致
④ description 是否非空、前 500 字符内是否包含触发词
⑤ 是否使用了 disableModelInvocation / modelInvocable / userInvocable 这类旧键名
⑥ 文件是否放在会被扫描的位置:<repo>/.dsh/skills/<name>/SKILL.md 或 <repo>/.agents/skills/<name>/SKILL.md
结论:能加载 / 不能加载 + 原因 + 最小修复
再确认项目根:从你打开会话的工作目录往上,第一个含 .git 的目录才是 <repo>。放错层级(例如 skills/foo/bar/SKILL.md,多嵌了一层)不会被发现。
验证:修好后新开一个会话,让模型列出它可见的 skill 名称。
$DSH = 'D:\softwarr\deepseekHARNESS\resources\runtime\cli\bin\dsh.cmd'
& $DSH --help # 启动器自身的参数
& $DSH web --help # web 表层的参数
& $DSH --profile web --dump-config # 当前生效的完整插件树
& $DSH --profile web --dump-default-config # 只看 bundle 默认值
& $DSH --profile web --dump-config-schema # 补丁条目的 JSON Schema(字段真源)
& $DSH --profile <名> --patch .\extra.yml --dump-config # 验证一份实验补丁
& $DSH rescue --from-default-profile web # 从官方模板派生一个自己的 profile
& $DSH plugin --profile web add <包名> # 往 profile 装插件(之后要重启)
& $DSH headless "跑一下测试" # 一次性任务,不用打开 GUI
会话内的斜杠命令:/plan [消息]、/plan off、/permission [预设]、/compact、/goal、/<skill 名>。
<填写:例如“面向内部运营的 NestJS 后台服务,含 Vue 管理台”><填写>;包管理器:<填写:pnpm/npm/uv…>apps/(可运行程序)、packages/(库)、tests/(跨包测试)pnpm install # 安装依赖
pnpm run dev # 本地启动
pnpm run test:unit # 单元测试(提交前必跑)
pnpm run typecheck # 类型检查
pnpm run lint:fix # 格式化与静态检查
跑某个包:pnpm --filter <package> test。
| 路径 | 放什么 | 不要放什么 |
|---|---|---|
apps/ |
可独立启动的入口与它们的装配 | 业务通用逻辑 |
packages/ |
可复用库,按领域分包 | 跨包引用具体实现的胶水 |
migrations/ |
只追加的数据库迁移 | 修改历史迁移 |
docs/ |
决策与使用说明 | 临时草稿 |
// 这是一段循环 这类注释。catch {} 吞掉;确实要吞,写明吞的是什么、为什么安全。console.log。README.md。pnpm run test:unit 与 pnpm run typecheck 的实际输出(或明确说明未能运行的原因)。--passWithNoTests。migrations/ 下已提交的迁移文件;需要变更就新建一个。git push、不 git commit --force、不改写已推送的历史。~/.dsh/AGENTS.md(不随仓库走)。AGENTS.local.md(默认在 .gitignore 里)。这是「可执行流程」类 skill 的模板:重点在判定标准和停止条件,而不是把命令抄一遍。
不适用:只想看单个函数的用法(直接读代码或文档)。
# 只跑受影响的包
pnpm --filter <package> run test
# 需要按文件名过滤时
pnpm --filter <package> exec vitest run tests/<file>.spec.ts
选择依据:本次改动触及的包 + 引用了这些包公共 API 的包。全量只在用户明确要求、或改动跨多个包时跑。
| 类别 | 判断依据 | 下一步 |
|---|---|---|
| 真实回归 | 断言差异指向本次改动涉及的行为 | 修代码,重跑 |
| 环境问题 | 缺依赖、端口占用、路径不存在、需要密钥 | 记录环境差异,说明如何在本地复现 |
| 测试自身过时 | 断言写死的是被本次变更有意改变的行为 | 同步更新测试,并在汇报中说明 |
| 偶发/时序 | 重跑一次即通过,且无共享状态假设 | 标注为可疑并发问题,给出再跑一次的结论 |
判定不清时不要猜:写明「无法区分 A 与 B,需要 <哪一个信息>」,并把已排除的选项列出来。
命令:<实际命令>
结果:<通过 / 失败 N 项 / 未能运行(原因)>
失败 1:<用例名>
类别:<真实回归|环境|过时断言|偶发>
证据:<报错首段>
归属:<哪个包/文件>
建议:<下一步动作>
--passWithNoTests、skip 用例或注释掉断言。这是「项目规范」类 skill 的模板:把规范写成 agent 能执行的检查步骤,而不是一堆形容词。 复制到
<repo>/.dsh/skills/project-standards/SKILL.md。
不适用:纯文档排版、纯配置值调整(那属于 run-tests 或直接改)。
AGENTS.md,确认命令与目录约定(它优先于本 skill)。apps/ 还是 packages/。| 项 | 判定标准 | 不通过时的处理 |
|---|---|---|
| 命名 | 与该包既有文件命名风格一致(camelCase/PascalCase/kebab-case) |
改成与邻居一致 |
| 错误处理 | 能失败处显式抛出;空 catch 必须注释说明吞掉了什么 |
补错误类型或注释 |
| 日志 | 用仓库 logger;不出现 console.log |
替换成 logger |
| 依赖 | 新增依赖有理由,且仓库内已有等价能力优先 | 去掉依赖或写明理由 |
| 公共 API | 未删除/重命名对外导出,或已列出全部调用方 | 补调用方改动 |
| 文档 | 受影响的 README.md 同批更新 |
更新文档 |
| 注释 | 只解释为什么,不复述代码 | 删掉复述性注释 |
改动完成后,输出实际执行的命令与输出,不要只写"应该能通过":
pnpm --filter <package> run typecheck
pnpm --filter <package> run test
任何一项没跑,必须写明原因(环境缺依赖 / 需要密钥 / 用户要求跳过)。
vibe-coding-guide/
README.md
validate-guide.py
vibe-coding-guide.md
appendix/
copy-paste-recipes.md
examples/
AGENTS.example.md
profile-patch.example.yml
settings.example.yaml
agent-presets/
skills/完整源码 Gitee 仓库浏览 →