以角色团队的方式干活:产品经理先写 PRD 或 DoD 并等你确认,再启动架构师、工程师、QA 与各类评审;每个角色的工具集按角色锁定,彼此通过磁盘文件协作。
Runs work as a crew of role agents: a product manager writes the PRD or DoD, waits for your confirmation, then starts architect, engineer, QA and reviewers whose tool sets are locked per role, and shares work through files on disk.
安装
dsh plugin --profile web add github:stuarthu/dsh-crewGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
安装与环境配置指引、插件开发教程见 DSH 中文社区文档 ↗
安装即在你的机器上以你的权限运行第三方代码——它可读写文件、使用凭据、访问网络,DSH 的工具审批不会为插件代码加沙箱。「检测到 manifest」仅代表发现 dsh.bundle / dsh.plugin 清单,不构成兼容性或安全审查;安装前请审阅源码,不熟悉的插件先在不含密钥的环境试用。
README
目录
English | 中文
在 DeepSeek Harness (dsh) 里, 用一个小团队(多个角色 agent)来完成工作。
你自己的 dsh 会话会变成产品经理(PM)。PM 是唯一直接和你对话的角色。它先写清楚 "什么算做完",请你确认,然后启动架构师做设计、工程师写代码、评审来把关。 各角色之间不能互相说话——他们通过磁盘上的文件协作,消息由 PM 转达。
0.9.0 版本。
- "什么算做完"写成你自己仓库里的一节,而不是一份会被丢掉的文件。
- 编程语言和技术栈先定下来,开工之前由你批准。
- QA 的用例留在磁盘上,后面每个任务都会再跑一遍。
- 每个做完的任务都会把四道评审记进你的仓库:任务小节顶上一行。
- 范围或契约的每一次变更,都有一份书面的变更请求文档。
- 推送必须有你的许可,推完之后 PM 会盯着 CI。
- 作业中断之后还能接着干。
- 角色:PM、调研、架构师、工程师、QA、代码评审、安全评审、文档评审。
两个平面
dsh 把面向模型的工具放在**agent 预设(preset)**里,而不是 profile 里。dsh-crew 遵循 这一点,把自己拆成两半:
| 部分 | 放在哪 | 为什么 |
|---|---|---|
| PM 规则 | 宿主平面(你的 profile) | 它不需要任何工具,所以在任何预设、任何会话里都生效 |
| 角色工具 | crew agent 预设 |
角色的白/黑名单会在子 agent 启动时按预设的工具集校验,名字必须和预设定义在同一个地方 |
装好插件后第一次启动 dsh,预设会被写入 $DSH_HOME/.agent-presets/crew。
想用角色,就把会话切到 Crew 预设。在别的预设里,PM 依然是 PM,它会发现自己没有
角色工具,并请你选择:换到 crew 预设,还是由它自己独立完成。
crew 预设就是 dsh 自带的 standard 预设,只改了一处:去掉 subagent、
subagent_fork、workflow、ralph 和产品化子 agent,换成 crew 角色。所以在这个预设
里,只有 crew 角色能启动 agent。
为什么团队是"扁平"的
dsh 对 agent 有三条硬规则,本设计完全按它来:
| dsh 规则 | 在这里意味着 |
|---|---|
| 消息只能发给直接子 agent | 每个角色都是 PM 的直接子 agent,PM 能联系所有人 |
子 agent 只能回复直接父 agent(report) |
所有回答都回到 PM |
| 两个子 agent 之间完全不能通信 | 角色之间用文件协作,不用聊天 |
如果由架构师去启动工程师,PM 就完全联系不到工程师了。所以只有 PM 能启动 agent。 这一条由四道彼此独立的保险守住:
- 每个角色都被禁用了全部 crew 委派工具;
- 每个角色工具都设了
maxDepth: 1,所以 crew 的子 agent 不能再启动一个 crew 子 agent——而且这一道不依赖任何工具名,改预设也削弱不了它; - crew 预设本身还去掉了其他所有能启动 agent 的方式,角色无法绕过名单从
workflow、ralph或裸subagent走; - dsh 自己在发消息时查血缘:兄弟不是孩子,所以哪怕角色手里有那个工具,消息也会被拒绝。 这一道不点任何工具名、不靠任何提示词措辞,所以改过滤器或改 persona 都削弱不了它。
"角色"到底是什么
角色不是 PM 临时粘贴的一段提示词,而是基于 @deepseek-ai/dsh-tool-subagent 生成的
真实委派工具:
| 角色 | 工具 | 人设文件 | 可用工具 |
|---|---|---|---|
| 调研 | crew_researcher |
roles/researcher.md |
只有 read、glob、grep、write、web_search——没有 shell |
| 架构师 | crew_architect |
roles/architect.md |
除 crew 工具外都能用 |
| 工程师 | crew_engineer |
roles/engineer.md |
除 crew 工具外都能用 |
| 测试工程师 | crew_test_engineer |
roles/test-engineer.md |
除 crew 工具外都能用 |
| 代码工程师 | crew_code_engineer |
roles/code-engineer.md |
除 crew 工具外都能用 |
| QA | crew_qa |
roles/qa.md |
除 crew 工具外都能用——它必须真的跑起来 |
| 代码评审 | crew_code_reviewer |
roles/code-reviewer.md |
只有 read、glob、grep |
| 安全评审 | crew_security_reviewer |
roles/security-reviewer.md |
只有 read、glob、grep |
| 文档评审 | crew_doc_reviewer |
roles/doc-reviewer.md |
只有 read、glob、grep |
所以评审无法修改文件,即使它自己想改也不行。人设会作为那个子 agent 自己的系统 提示词固定下来。
每份人设里还各有一节 "你能写什么":这个角色能写哪几类文件,以及哪几类它必须拒绝
——哪怕简报把它递过来也不写,首先就是那份判它自己工作的文档。读不受限。 这十节
都在 roles/ 里,装好这个包就能自己读。
这九个角色里有三个会去做一个任务,PM 启动哪一个取决于任务的形状:crew_engineer
一个人把一个任务的单元测试和产品代码都写掉,这是默认;而 crew_test_engineer 和
crew_code_engineer 把一个任务拆成两半——一个只写单元测试,另一个只写产品代码,而且
在写的过程中谁也看不到对方那一半。下面双人形状那一节讲它怎么跑、它证明什么。
评审改用"白名单",是两次实测逼出来的:
- 只禁
write和edit时,它用echo hello > file照样建出了文件——shell 本身 就是写文件的工具。 - 连
bash也禁掉之后,它自己报出来的工具里仍有workflow、ralph和一整套控制 桌面的 MCP 工具——每一个都是缺口。
黑名单永远列不全"以后才装上的工具",白名单不需要列。diff 由 PM 贴进评审任务里, 需要跑的命令也由 PM 代跑。
修改角色
角色人设就是 roles/ 下的普通 markdown。把文件复制到 ~/.dsh/crew/roles/,用同名
即可替换自带版本。唯一限制:提示词里不能出现 {{——dsh 会把它当变量解析,插件会在
启动时直接报错并告诉你是哪个文件。
角色的工具名单和按角色指定模型,配置在角色所在的位置:
~/.dsh/.agent-presets/crew/agent.cordis.yml 里的 dsh-crew-roles 那一行。
这个文件在装好的 preset 文件夹里,而 dsh-crew 升级时会整个替换该文件夹。你改过的
文件会以 agent.cordis.yml.bak 的名字留在旁边,启动日志也会点名——但里面的设置
不会自动回来。升级之后,请把你的改动重新抄进新文件。
一次作业怎么跑
PM 先把你的需求分到两条通道之一:
ask(只回答)或team(完整流程)。 任何大小的改动——一个错别字、一次改名、一行修复、一整个功能——都走team, 并且都会有一个里程碑:里面至少一个任务、一轮 QA,以及代码、安全、文档三个 评审各一轮。一个里程碑不等于发版:它是"一次完整循环加一次提交";推送和打 tag 在它之外,每一次都还要你自己单独同意。分不清你要的是答案还是改动时,它会问你。它会问用哪种语言。它绝不猜。
它会访谈你,而这场访谈是有方法的:一轮只问一个问题,每个都带推荐答案, 你回答之后它才问下一个,绝不一次丢给你一串。它会按"自己缺的是哪一类东西"来挑 问题的种类,其中一类是"这件事本身是不是该问的问题?"——那是它可以说"你可能在解 一个错的问题"的许可,而且要早说,趁改方向还便宜的时候。它会在"开局文档每一节都 能写下来、不留一处猜测"的那一刻停下来。它会先在仓库里查清所有能查到的事实;凡是 "查一下"不够的,它会启动
crew_researcher:每条结论都要给出来源——所以只有文件 回答不了的问题才会问到你。一个问题你可以选择"先不定"。 每个问题 PM 都只判一件事:它的答案会不会改变 "要造出什么"、"要发出什么"。两样都不改变,这个问题就能跳过——光是"不回答也能开工" 还不够。能跳过的问题,除了推荐答案,还会多给你一个**"先不定"。你选了它,那件事 在开局文档里一个字都不写**——面谈定下来的那张表里不写,"不在范围内"里不写, "还没定的事"里也不写。所以你以后想要它,直接说就行,没有什么要推翻。至于这次开口 要不要走一次变更请求,跟别的请求一样判。有些问题的答案确实会改变要造出什么、 要发出什么。这种问题没有这个选项,PM 会用一句话告诉你它为什么跳不掉。
"不在范围内"只列有真代价的东西。 一条能进这张单子,只有三种理由:越过去要把 已经做完的活重做一遍;它撤不回来——包发出去了、tag 推了、数据删了;或者它会削弱 一条安全护栏、一条权限规则。PM 只是顺手没做、越过去也就多干一点活的东西, 不写进去。这让单子既短又诚实:在单子上的每一条都是一堵墙;不在单子上的, 你想要就直接说。
它先定下编程语言和技术栈,并且要你批准。 如果仓库里已经有一套,那就是它。PM 会去读依赖清单、锁文件、测试目录和 CI 配置,说明它看到了什么,你一句话确认即可。
只有当真的需要做选择时——空仓库、新服务——PM 才会先启动
crew_researcher。调研角色 会报告:这类项目现在通常用什么来做(每条结论都要给出来源)、这台机器上已经装了什么、 每个选项的代价分别是什么。它只列选项,不许给出推荐。然后由 PM 给出它推荐的那一个,并写明备选是什么、为什么没选它,把一段 语言与技术栈 写进文档里:语言和版本、包管理器、框架、数据库,以及测试框架和 确切的测试命令。最后这一项最重要:工程师用它写测试,QA 也用它写用例,所以只能有 一个答案,不能有五个。
你和文档一起确认这套技术栈;确认之后,它只能通过 CRD 才能改——CRD 是"变更请求文档",下面会细说。项目里已经有的库, 工程师可以自己挑;要新增一个依赖,必须回来问 PM。
它写出开局文档。一件作业一份,而且文件名里带着这件作业:
docs/design/prd-<日期>-<作业 slug>.md(PRD,product requirements document, 产品需求文档)——小活也用它,真正的产品也用它。名字这两半都少不了:同一天可能开 两件作业,而固定的文件名会悄悄覆盖上一件作业的 PRD。设计文档同形,叫docs/design/hld-<日期>-<作业 slug>.md;而docs/design/tasks.md保持原名—— 它是全仓库一张表,不是一件作业一张。 轻重在内容里,不在文件名上:小活的 PRD 就是三段话,目标、不做的事,以及那段 语言与技术栈。它会说明它把这件活判成多大,一个词就能改。开工前必须你确认。 大活的 PRD 会被切成里程碑(milestone):三到六个停靠点,每一个都是你能亲眼 看到、能自己判断的东西,用你的话来写,而不是用代码的话来写。M1就是概念验证 (PoC):把最有风险的那条路打通一条最细的真实链路,真的跑起来。里程碑清单要单独 给你确认一遍,因为它决定了你在什么时候有发言权。"什么算做完"是一节,永远不是一份单独的文件。 每个里程碑都带一节 DoD (definition of done,"什么算做完"),任务表
docs/design/tasks.md里的每一行 任务也各带一节。一节 DoD 说两件事:这一件事怎么算做完,以及别人怎么验—— 哪个 QA 用例、哪条确切的命令。两者都在你的仓库里,所以作业结束很久之后,当初 承诺过什么仍然读得到。再也没有dod.md,也没有全局编号的验收检查表:一条 检查就是"T-05 的 DoD 第 2 条",写在它要管的那件事旁边。它先把你给的作业名转成一个短 slug——只用小写字母、数字和
-,别的都不要——把 转好的 slug 告诉你,再用它创建crew/<job-slug>分支。然后,如果是大活, 它会启动crew_architect,产出高层设计、决策记录(ADR),以及任务表docs/design/tasks.md——每一行都带一节 DoD。小活没有架构师,所以那张同样的表由 PM 自己写,位置一样、形状一样,变的只有打字的人。拆模块,每条边界一份契约。 架构师还会把系统拆成模块——先找仓库里已经有的 东西来复用,再考虑新建——并且当两个或更多模块之间要互相调用时,为每一条边界写 一个契约文件,放在
docs/design/api/:两边怎么通话(进程内调用、HTTP、gRPC、 事件消息等)、数据格式、每个调用的输入、输出和可能的错误,以及这个形状怎么让 调用方不容易用错。它只定风格,不定具体的库。契约为什么要紧。 这些契约正是两个工程师能同时开工的前提,因为 crew 角色 之间不能互相说话。正因为不能对表,每个契约还会为两边各指定一个测试:被调用 方证明自己的回答和文件写的一模一样,调用方则针对按文件搭出来的桩(stub)来测。 另外,只要存在边界,第一个任务就是走骨架(walking skeleton):由一个工程师 单独把最有风险的那条边界打通一条最细的真实链路,跑通之后其他任务才并行开工。 契约对不上,在这里修最便宜。
每个任务都要归到你确认过的某个里程碑下面——架构师不能增加、改名或调整这些里程碑 的顺序。之后必须由
crew_doc_reviewer全部通过,才允许写第一行代码。它为每个任务启动一个
crew_engineer,一次只做一个里程碑。 只有当两个任务的文件列表不重叠时,工程师才会同时跑,而且绝不跨里程碑同时跑。每个工程师都先写测试:先写一个单元测试, 跑一遍,确认它是因为"功能还不存在"而失败,然后才写刚好能让它通过的最少代码。 它的汇报里必须给你看先失败的那次运行,再看通过的那次运行。每一个这样的测试都是 一个真实的文件,放在你项目自己的测试目录里,写在任务行的文件清单中,并和代码一起 提交——绝不是谁在 shell 里跑过一次的命令。如果工程师认为某个任务 没法先写测试,它必须先问 PM,在得到答复前一行代码都不写。一个任务做完的判据,是它自己的单元测试通过,别的都不拦着它:QA 和三个评审 这时还没跑,所以它们谁也不负责宣布一个任务做完。那一行任务仍然记四个结论,而一道 还没跑的检查,就老实写成
not run并给出理由,绝不写成pass。每一行任务还带一个形状,默认是
solo(单人)。 单人就是上面这一段,第二种形状 出现之后,它一个字都没改。标了pair(双人)的那一行,由两个永远碰不到面的工程师 来做:一个只写单元测试,另一个只写产品代码。这份清单后面的 双人形状那一节会讲:它买到什么、PM 怎么跑它,以及一次全绿证明不了什么。QA 和三个评审一个里程碑只跑一轮,在里程碑最后跑——不是每个任务跑一遍。PM 在 最后一个任务落地、编码停下来之后才开始,因为一条阻塞发现会改动代码,把早跑的检查 作废。只有改动过的部分在范围内,这个里程碑之外的东西都不在——评审员再不喜欢 别处的东西也一样。
先一轮 QA,分两步。 先一个
crew_qa把各任务的 DoD 章节变成一份用例清单, 一条一行,别的什么都不写——它不读代码,因为被测的那一方不该出题。PM 读完这份 清单,然后一条用例一个 agent,全部并行铺开:每个 agent 把自己那一条写成真实的 测试文件,放在docs/qa/<task-id>/,用你项目自己的测试框架,旁边配一个run.sh, 并把整套测试跑一遍。然后另外三个,在同一条消息里各一轮、并行:
- 代码评审——先看正确性,再看驱动这次改动的测试,然后是复用、能否更简单、 可读性,以及是否符合本仓库自己的代码风格。后面这四项评审员也可以判定为 "阻塞",但前提是它必须给出它想要的具体替代写法;给不出就只能记为"可选"。
- 安全评审——仅当改动涉及网络、登录鉴权、密钥、项目外的文件、shell、 用户输入、客户数据或新依赖时才做。这张清单就是"有风险"这个词的全部判据, 没有第二张。
- 文档评审——这个里程碑改过的文档,一份文档一个 agent。
只有因为某个评审自己的发现而做的改动,才会把那个评审叫回来:代码改动重跑代码 评审,文档改动重跑文档评审,安全改动重跑安全评审。三个从不一起重跑,而第二轮只 复查"阻塞项"。要是两边仍然谈不拢,PM 会停下来,把双方的说法都摆到你面前。
代价,明说,因为这是知情之后选的。 一轮放在最后,缺陷会被更晚发现,上面已经压 了更多活,所以返工面更宽。它换来的东西要求那一轮必须是完整的一轮:每个任务 DoD 章节里的每一条,不管测试跑出来是什么结果。
QA 的用例留在磁盘上,计划不留。 用例一写出来,同样的内容就有了能跑起来的形式, 所以计划随作业一起丢掉。计划里只有一段不能丢:"有什么我在这里测不到、为什么"—— 那一段写进
docs/qa/gaps.md,一份常备的清单,说明你这个产品里有哪些东西没有用例 能判,后面的作业会把它一条条变短。bash docs/qa/run-all.sh会跑所有任务的用例, PM 还会把这条命令接进你项目默认的测试命令里,这样早先的用例不靠谁记得就会守着 后面的改动。老用例开始失败就是"阻塞级"的回归缺陷,任何人都不许把它改绿。如果你的 测试运行器看不到那个文件夹,PM 会加上让它看得见的那一行配置;"这些用例跑不了"是 PM 要拿来问你的问题,不是可以就此停下的结论。PM 负责提交——工程师完全不碰 git。只暂存该任务拥有的文件,绝不
git add -A。里程碑评审——PM 会停下来问你。 当这个里程碑里的所有任务都过了上面那几道关 并且已经提交,PM 会向你汇报:现在能做什么了、你自己动手试一试的确切命令、哪些是 故意还没做的、测试结果,以及发布方面到了哪一步。然后你来决定:发布这个里程碑、 先不发布继续做、要改点什么、还是停下——一个问题,四个答案。如果你要改 的东西动到了 PRD,计划会先回到架构师和文档评审员那里,之后才允许继续写代码。 上一个里程碑你没答复,下一个就不会开始。小活没有这一次停下来的评审:它就是一件事, 最后汇报一次。
决定要发布的里程碑,会有两份计划;它们长什么样是查出来的,不是猜的。 这类计划差别很大。npm 包发出去的版本撤不回来。手机 App 要等应用商店审核。网站服务 靠重新部署回滚。数据库表结构需要一份能安全跑两次的迁移脚本。
所以 PM 会启动 crew_researcher,去查你这类项目的这两份计划通常包含什么,每条
结论都要有来源和日期。它会先读你仓库里已经在做的事:CI 配置、更新日志、已有的标签、
发布脚本。然后它写下两个文件:
docs/release/<milestone>-release.md——版本号和定它的规则、给用户看的发布 说明、按顺序的确切步骤和每一步谁批准、开始之前必须成立的前提、事后怎么确认真的 成功了,以及怎么撤回。如果撤不回来,计划里就直接这么写。docs/release/<milestone>-upgrade.md——谁在从哪些版本升上来、每一处破坏性 变更以及用户必须做什么、迁移步骤以及能不能安全跑两次、跳过一个版本会怎样、怎么 退回去以及会丢什么数据、要花多久,以及期间什么会停服。
不发布的里程碑不写计划,只给一份发布差距清单,就放在你仓库里的
docs/release/<milestone>-gaps.md:一段老实话,说明它不发布,以及还缺什么才够得上
发布。下一个里程碑会把同一个文件改得更短。另外,批准计划不等于批准
推送——每一次推送和发布,都还要单独再问你一次。
12. PM 会把面向读者的文件更新到与成果一致。README.md 永远是英文;如果这次作业你选了
别的语言,它会在旁边再维护一个内容相同的文件,例如 README-zh.md、
README-ja.md。只要这次改动是用户能察觉的,它还会在 CHANGELOG.md 里加一条;
如果你仓库自己的规则或目录结构动了,它也会改 CLAUDE.md。
如果这次改动读者根本看不到,它就不动这些文件,并在总结里说明。
13. 最后再由 crew_doc_reviewer 收一次尾——这是第 8 步那轮文档评审的尾巴,不是
第二轮。它只读那一轮之后才落地的东西:上面那些面向读者的文件,README 也在内。它
检查文档能不能照着开工、是否前后一致(同一个东西只用一个名字、格式统一、多语言
版本内容一致),以及是否好读——读者设定为大约 14 岁、母语不是英语的人。它靠"数"
出来判断:句子多长、有没有俚语、有没有没解释就用的术语,而不是凭口味。措辞问题
它也可以判为"阻塞",但前提是它必须自己写出替换的句子。
14. 推送与 CI,前提是你许可。 PM 先确认远端、workflow 和可用的 gh 都在,然后
每一次推送前都问你——包括修完之后的再次推送。它只推你说"可以"的那些——crew/*
分支、main,或发布标签——盯住这次运行,CI 挂了就把真实报错发回给拥有这些文件的
工程师。
15. 合并与清理,只在你要求时才做。 PM 会自己把 crew/<job-slug> 分支合并进
main。它会分三次问你——一次为了合并,一次为了推 main,一次为了删分支——一次
"可以"绝不覆盖下一件事。合并永远不用 squash,所以"一个任务一个提交"和它带的
test-first 证据都留在历史里、还能读。推 main 之前,它会告诉你这次推送会不会
触发发布类 workflow,并点名它读过的文件;你如果还是说"可以",它就推。它只有在
证明了工作真的已经合并、并且真的已经在远端之后,才会问要不要删分支——包括证明
远端分支上没有 main 里没有的东西:git push origin --delete 本身没有任何保护。
设了 trustRootAgent: false 时,远端删除会被特意拒绝;这时 PM 会把命令交给你
自己执行,而不是重试。工作分支就那样留着,也是一种正常的结局。
16. 一个 bug 会变成任务表里的一行,而且"修好算什么样"由 PM 在动手之前写。
一个真的 bug——你报的、QA 找到的、评审发现的——会在 docs/design/tasks.md 里
拿到自己的一行,由 PM 在任何工程师动手之前写好。那一行装两样东西:报上来的现象
(谁看到的、什么命令、发生了什么、本来期望什么),以及它那一节 DoD:必须存在
并且必须通过的那个失败用例,还有必须改变的那个行为。修它的工程师永远不写这一节。
测试先行确实会产出一个测试,但那是修的人自己写的——这正是"只修了症状"能过关的
方式:在它动手之前,没有第二个人说过"修好的标准是什么"。改一个错别字这种一行的
修复不走这套:那仍然只是一条写得好的提交信息。
**然后,修它的过程可能会来问你,而且每一次选择都会被写下来。** 这件事可能在第 8
步里的任何时候发生。工程师修一个 bug——QA 报的缺陷、代码评审的阻塞发现,或者它
自己撞见的 bug——会先找出至少两个真的可行的办法。如果这几个办法只是写法不同,它自己
挑一个,并在汇报里说明它比较过哪几个。如果差别会留在代码里,它就停下来。下面这
六条里只要有一条在几个办法之间不一样,差别就留在了代码里:
- 哪个模块为这个行为负责;
- 检查或修正放在哪一层;
- 会不会碰到 `docs/design/api/` 里的模块边界契约;
- 会不会改公开的名字、命令、配置项或输出格式;
- 你看得见的行为会不会变;
- 快慢或兼容性会不会变。
停下来之后,它把这个 bug 的病因和它找到的每一个办法交给 PM:每个办法要改哪些
文件、代价是什么、以后会痛在哪里,还有它自己推荐哪一个。然后 PM 按 CRD 那条同样
的分界线来定:差别你看得见的,它当场就问你;差别只留在代码内部的,它自己定,并在
下一次里程碑评审时告诉你。新功能和重构不走这条路。
决定会先写下来,之后才允许开工,而且里面要有**全部选项**。不管活多大,它都只有
一个去处:一条 **ADR**——放在 `docs/decisions/adr/` 的决策记录。大活可以
由架构师来写;小活没有架构师,就由 PM 自己写。ADR 里有:
这个 bug 的病因、每一个选项及其代价、
以后会痛在哪里、**为什么它输了**、选中了哪一个、是谁定的,以及理由。选项那一节
**原样引用工程师自己写的那份问题文件**,PM 只补"决定"和"理由"两节——这样它没法
悄悄把选项改成对自己的决定有利的形状;也不许写成"选项:见 Q-03",因为那个文件会
随作业一起丢掉。**每条 ADR
都是写给你看的**:一个从没读过代码的人也要能分清这些选项的差别,而且推荐的那一个
会被标出来。设计不会停下来等你挑——架构师照自己推荐的那一个继续做,而 PM 会在里程
碑评审时,把这个里程碑期间每一条 ADR 的选项摆在你面前。你可以推翻其中任何一条;
那就是一次 CRD,已经照旧方案做完的任务会重做。
双人形状
docs/design/tasks.md 里每一行任务都带一个形状,默认是 solo(单人):一个工程师
先写一个会失败的单元测试,再写刚好能让它通过的代码,就是上面一次作业怎么跑里写的
那样。
另一种形状是 pair(双人):把一个任务分给两个永远碰不到面的工程师。
crew_test_engineer只写这个任务拥有的单元测试文件。crew_code_engineer只写产品代码。- 两个人各在自己的一棵 git 工作树(worktree)里干活。在两半还在写的时候,单元测试 根本不在写代码那一半的树里,所以那是"读不到",不是"不该读"。
- 两个人读同样的两份文档,别的都不读:那一行任务的 DoD 一节,以及架构师用来 钉死两半之间那条线的接口 ADR。
- 两个人之间不能对话。这不是礼貌问题,是平台决定的:兄弟 agent 不是自己的孩子,所以 哪怕角色手里有那个工具,消息也会被拒绝。
- 由 PM 合并两半,并且恰好跑一次项目自己的测试命令,然后把跑出来的结果原样报告。
这是独立验证(independent verification),安全关键工程里用的那一种:两个人在不说话 的前提下各读一遍同一份文档,这样两份读法不一样的地方就会显现出来,而不是被谈平。
它不是结对编程,而这个对比正是把它说清楚的最好办法。两个人坐在一个键盘前会持续 沟通、持续检查,他们的目标是收敛成一份共同理解。本形状把沟通全部拿掉,要的正好 相反:两份读法不许收敛,因为它们不一样的那个地方才是全部意义所在。所以它不是 "把聊天关掉的结对编程",它是另一门东西,本仓库里一律叫它双人形状。
它买到什么。 测试先行给你的是一个在代码存在之前就红过的单元测试。但在单人形状里, 那个单元测试是由马上要写代码的同一个 agent 写的,所以它可能被写成迎合那个 agent 本来 就打算写的代码。双人形状从结构上拿掉了这个可能:写检查的人故意不是写代码的人。它买到 的第二样东西更大——对同一份文档的两次独立阅读。文档在哪里允许两种读法,两半就在 那里对不上,而你是在合并时发现它,不是在线上发现它。这里的分歧不是意外,它是你能拿到 的最便宜的信号:一份大家都已经点过头的文档其实并不清楚。
PM 怎么跑一个双人任务
开两棵 git 工作树,一半一棵,各在自己的一个分支上,都从同一个基点长出来:
git worktree add -b <tests branch> <tests tree path> <base> git worktree add -b <code branch> <code tree path> <base>新开的工作树里只有 git 跟踪的东西。你项目自己的检查除此之外还需要什么,都要在这 同一步里、在任何一个工程师收到简报之前,放进两棵树里。少了它不会报错—— 检查会安静地变弱:一道检查跑不了自己的一部分时,它可能会出声说明然后继续,而 整次运行照样是绿的。在本仓库里,这就是每棵树一条软链接;少了它,
tools/verify-mount.mjs会跳过角色工具那一半,而那棵树看起来仍然是绿的。两半在同一条消息里收到简报、同时开工,谁都没有先手。每份简报带着这一半自己的 工作树路径、只带这一半的文件清单(两份清单永不重叠)、那一行任务的 DoD 一节, 以及接口 ADR 的路径。
首次会合。 PM 合并两半,跑一次项目自己的测试命令,把输出原样报告。它绝不改点 什么再跑一次去换一个更好看的结果:重复这次运行会让整套东西塌回普通的测试先行, 而且是最坏的一种——每一处不一致都被读成"代码错了"然后改掉,一次分歧都不会被上报。
红灯会让两半各自回去查自己那一半,一次。 那之后仍然对不上的,就是分歧,而且要 写下来:文档说了什么、每一半从里面读出了什么、两份读法在哪里分开。由 PM 定;两种 读法都站得住时,它把这件事交给你。写单元测试那一半永远不许为了消掉分歧而弱化 断言;只有 PM 能批准改动一个单元测试的要求,而且那个改动必须能追回 DoD 一节的原话。
修是在合并后的树里写的,在那里写代码那一半已经能读到单元测试了。独立性到此 结束,这是明知故犯:那一半独立的读法已经落在盘上、已经进了证据,之后还硬把它蒙住 换不到任何新信号,只会让修变难。
PM 删掉两棵工作树和两个分支,并把三份证据交给代码评审:写单元测试那一半的红灯、 首次会合那一次的结果,以及分歧记录——那次会合是绿的时候,这份记录是空的。
它在哪存在,在哪不存在
- 只在有架构师的作业里。 两个工程师在写第一行之前,必须落在同样的五件事上:
从哪里 import、导出的名字、签名、返回值的形状、出错时会怎样。他们看不到对方,所以
这五件里任何一件落得不一样,合并后那次运行就是红的,而这种红谁也学不到东西——那是
撞名字,不是分歧——而且它发生得太频繁,真正的信号会被淹掉。架构师把这五件钉在
接口 ADR 里,而且只有架构师能改它。小活没有架构师,所以小活的每一行都是
solo。 - 两半必须动同一个文件时不能用。 双人任务的两份文件清单不许重叠,而一个文件不可能
同时在两份清单里。要么把任务拆到两半拥有不同的文件,要么它就留在
solo。 - 它随它所在的那张表一起确认,绝不逐行问。 架构师写任务表时会给每一行提一个形状。 小作业里 PM 自己写那张表,你连开场文档一起盖章——但小作业根本没有双人形状。 大作业(双人任务唯一能存在的那条路)里,架构师是在你确认完开场文档之后才写那张表的, 所以由 PM 确认形状,你在里程碑评审时看到它们。两条路都是一张表一次点头: 五十个任务的作业不等于五十个决定。架构师带来的是一整张表的一个默认值,加上一份例外 清单,每个例外都带它的理由:这一行的 DoD 一节它怎么写都写不锋利;这一行坐在一个模块边界 契约上;做错的后果是钱、权限或数据;这块地方以前的任务出过缺陷。
- 它更贵,而那个数字是估计。 同一个任务,双人大约比单人多花 35% 到 75% 的力气: 写的部分被拆成两半,但读文档那部分被做了两遍,而在小任务上读常常是更大的一块。 墙上时间可能反而更短,因为两半是同时写的。这些数字都不是实测。
三种会写"检查产品的东西"的角色
现在这样的角色有三个,很容易混,而且其中一个名字本身就在招人误会:
crew_test_engineer 是程序员,不是 QA。
crew_test_engineer |
crew_code_engineer |
crew_qa |
|
|---|---|---|---|
| 它是谁 | 程序员 | 程序员 | QA |
| 它写什么 | 单元测试 | 产品代码 | 用例:验收、黑盒 |
| 粒度 | 一个单元测试管一个行为 | — | 一条用例管一条 DoD 条目,按你会看到的方式验 |
| 时机 | 代码存在之前 | — | 代码写完之后 |
| 家 | 你项目自己的测试目录;任务拥有的文件,和代码一起提交 | 产品代码文件 | 只在 docs/qa/<task-id>/,别处没有 |
| 能看到代码吗 | 不能——它在自己的工作树里,那里还没有代码 | — | 写用例清单的那个 agent 不看;写单条用例的 agent 可以 |
| 范围 | 只有这一个任务 | 只有这一个任务 | 这个任务,外加之前每个任务的用例再跑一遍 |
四条区别,没有一条是可选的:粒度(一个单元行为 对 一条验收条目)、时机(代码之前
对 代码之后)、家(你项目自己的测试目录 对 docs/qa/)、范围(只有这一个任务 对
每个任务的用例作为回归再跑一遍)。
全绿证明不了什么
这一半比前面几节更值得读两遍,所以它写在这里,而不是缩成一句附注。
首次会合全绿,只说明一件事:两份读法对上了。 它不说明文档是清楚的,而且任何 报告——两个工程师的、PM 的、评审的——都不许声称它说明了这件事。一份报告如果把 一次全绿的首次会合写成"这一节 DoD 没有歧义",那对代码评审来说是一条阻塞级的发现, 因为以后会有人拿这句话往上盖东西。
一份文档有两种歧义,而本形状只抓得住一种。 一种让两个读者产生分歧,那正是双人形状 为之而生的那一种。另一种让两个读者从同一句含糊的话里读出同一个错意思,对这一种 本形状完全瞎:两半正好对上、运行全绿、什么都不会上报。这种瞎掉的情形很常见,而且是 实测出来的,不是担心出来的:横跨 5 个 harness、23 个模型、48 个实现,同时失败的次数 是独立性模型预测值的 3.7 倍(N-Version Programming with Coding Agents,arXiv, 2026-06),而且它们集中在规格说明书最薄弱的地方——也就是说,它是穿着"最好的结果"那身 衣服来的。给两半配不同的模型堵不住它:完全相关的失败换模型、换 harness 都还在,而一侧 用更弱的模型只会让 PM 被大量假分歧埋掉。所以两半是故意跑同一个模型的,而且 本形状不是最后一道网:QA——在后面、闭眼、按文档自己写用例——才是这个团队对 "共同误读"的那道网;而首次会合是绿的,也不会让代码评审的活变少一点。
还有一个天花板。 这套东西能买到的一切,上限就是那一节 DoD 的质量,而 那一节 DoD 没有第二双眼睛:没有谁会像这两个工程师对代码做两次独立阅读那样,去对 它做一次独立的第二遍阅读。这是本设计最根本的局限,写在这里,而不是留给你以后自己撞上。
重要的事不会只留在聊天消息里
crew 是扁平的:PM 和每个角色单独说话,两个角色之间永远不能通话。所以一条消息只到达 一个角色,然后就消失了。正因如此,crew 靠文档说话——角色的汇报指向它写下的文件, PM 的答复指向它改过的文档以及那份文档的新版本号。这样,正在做同一条边界两侧的两个 工程师读到的是同一个文件;明天才启动的角色,读到的和一小时前启动的角色一样。
在这之上,每一条变更请求都有自己的文件。只要有人——你、某个角色,或者 PM 自己——
提出的东西会改变你最终拿到的结果(范围、某条 DoD 条目、里程碑清单),或者会改变两个
模块之间怎么通话(边界契约),PM 就先写下
docs/decisions/crd/NNNN-<short-name>.md:谁提的、想要什么、为什么、会动到哪些文档和任务、
代价是什么,以及决定和理由。还没定的 CRD,一行代码都不会开工;被拒绝的 CRD 也会留着,
作为"这条路我们没走"的记录。
谁来定:
- 只改契约、你完全看不到差别的修补,由 PM 自己定。它写好 CRD,派架构师去改契约 文件,然后在下一次里程碑评审时告诉你。
- 凡是动到范围、某条 DoD 条目或里程碑清单的,必须你同意。 PM 写好 CRD 就停下来问你。 在你答复之前,不升版本号,也不开任务。
小问题不会变成 CRD:角色的问题只要文件能回答,就只是作业目录里的一条记录;代码上的 评审意见就是评审意见。只有范围和契约这两件重做起来最贵的事才配一个文件。 面谈时你选了"先不定"的东西也不算范围:它哪里都没写下来,所以你以后想要它, 不推翻任何一句已确认的话——就这一层来说,不需要任何 CRD。但如果答应它会动到里程碑 清单、某条 DoD 或者范围,那仍然是一次范围改动:PM 照旧写 CRD,照旧停下来问你。
"怎么做"的决定则写成一条 ADR,不分活的大小。 分辨两者只要一个问题:这件事是有人
要求的吗? 有人要求——你、QA、某次评审——那就是变更请求,写 CRD。没人要求,是干活时
撞上的选择,那就是 ADR,放在 docs/decisions/adr/。别的都不参与决定它的去处:不看活
多大,也不看有没有架构师。小活没有架构师,所以由 PM 自己写这条 ADR。
一份文档放在哪,取决于它能活多久。 比作业活得久的东西在你的仓库里,放在 docs/
下面,每个目录的名字就说清它装的是什么:PRD、任务表和设计在 docs/design/
(每条模块边界一个契约文件,在 docs/design/api/)——每一节 DoD 也跟着住在那里,
所以当初"算做完"的标准明年还读得到;决策记录和变更请求在
docs/decisions/(adr/ 和 crd/)、QA 可重复运行的用例和那份"哪些东西没有用例能判"
的常备清单在 docs/qa/、
每个要发布的里程碑的发布计划和升级计划在 docs/release/(不发布的里程碑,它的发布
差距清单也放在这里)、研究员的答案在 docs/research/。
只属于这一次作业的东西放在仓库外的 ~/.dsh/crew/jobs/<job-slug>/,这样你的
git status 保持干净:作业状态(state.json)、QA 的测试计划,以及
角色留给 PM 的 Q- 问题文件。整个目录会在作业结束时丢掉;而一次测试运行的输出从来
就不是文件。"什么算做完"故意不放在这里了:它是这件作业自己那份 PRD 或
docs/design/tasks.md 里的一节 DoD,在你的仓库里——因为一份单独的文件,就是一份会被
丢掉的文件。
丢掉之前,里面持久的那一半必须先搬出去。 这是作业收尾时一个真实的步骤,而且它排
在 PM 给你最终总结之后——不是 DoD 条目全绿的那一刻,因为想清楚一件事往往还要再往
后一阵。下次也要守的规则搬进 principles.md,"怎么做"的决定搬进一条 ADR,"做什么"或
契约的决定搬进一个 CRD,这次改动的理由和真实的测试数字写进提交信息,QA 那段"有什么我
在这里测不到、为什么"写进 docs/qa/gaps.md,还有两样是丢过一次之后才补上的——一条 DoD
条目自己的文字,以及一个任务拥有哪些文件,都搬进 docs/design/tasks.md。
"不需要了"必须是挣来的。ADR 把工程师的
选项原样抄进来、而不是指过去,也是同一个道理。
中断之后
光有状态文件还不够——下一次会话得知道它的存在。所以 dsh-crew 每一轮都会读作业 目录,只要还有没做完的作业,就把一段简短提示摆到 PM 面前:
Unfinished crew work: 1 job left in /home/you/.dsh/crew/jobs.
- "add-sso-login" in /home/you/project (branch crew/add-sso-login):
5 of 9 tasks done, 2 blocked. Last change 2026-08-18 09:12.
PM 必须先告诉你,再问一个问题:继续,还是重来。没有你的回答,两件事它都不会做。
属于别的目录的作业会被忽略;读不出来的状态文件会如实报告,而不是当作已完成。
把 resumeNotice 设为 false 可以整体关掉。
git 保护
host/git-guard.js 会检查每一条 shell 命令。你自己的会话是根 agent,被信任:它的 git
和发布命令直接放行。每个团队角色都是子 agent,保护会拒绝子 agent 发出的:
- 推送
main、master、trunk、develop、HEAD,或没有写明分支的推送; - 任何标签推送、远端删除、
--mirror、--all、强制推送; npm/pnpm/yarn/bun publish、npm dist-tag、gh release create;- 推送到"GitHub Actions 的 CI 在分支 push 时会发布"的仓库;
- 任何点名审批文件的 shell 命令——你自己的会话也一样,所以没有 agent 能用 shell
命令给自己授权。这个名字是按"整个文件名"来匹配的,所以只是包含它的更长的名字
不会被牵连:
crew/push-ok-flow分支、push-okay.md文件、push-ok.bak备份, 都不会被当成审批文件。
子 agent 的其他分支推送需要你创建一次性审批:
mkdir -p ~/.dsh/crew && touch ~/.dsh/crew/push-ok
一次推送用掉后,保护会立刻删除该文件。一次审批,只能推一次。
子 agent 被拒绝时,只会被告知"去请用户批准",不会看到上面那两条命令。只有你 自己的会话才看得到它们,所以刚被拒绝的那个 agent 不会同时拿到配方。
把 trustRootAgent 设为 false,就能让保护像对待子 agent 一样对待你自己的会话。
approvalFile 必须指向一个文件,不能是文件夹。写成 ~/.dsh/crew/ 这样,被保护的
名字就会变成 crew,所以保护会直接拒绝加载,并在报错里告诉你要点名文件本身。
三条老实话。每一条都是真的缺口,不是免责声明。
- 它是基于命令文本判断的,所以更像安全带,而不是一把锁。藏在脚本文件里的命令
仍可能绕过,由 shell 拼出来的文件名也一样绕得过。反过来也有代价:只要命令里
提到审批文件的名字,就会被拒绝,你自己的会话也一样,所以提交信息里带
push-ok的命令跑不起来。 - 它只读
bash和pwsh。 一个能写文件的角色——工程师就能——可以直接把审批 文件写出来,这个调用保护根本看不见。这里没有任何东西能拦住它;拦住它的是 dsh 自己的"写文件"审批弹窗。真正的关口仍然是 dsh 自己的审批弹窗。 - 发布型 workflow 的扫描只覆盖 GitHub。 保护只读
.github/workflows,也只认 GitHub 的on: push:写法。GitLab、CircleCI、Jenkins、Azure Pipelines 都不在 它的覆盖范围内,这是故意的:把 GitHub 的触发规则半途套到别的 CI 系统上会造出 误挡,而误挡比不挡更糟,因为它会教你不看内容就说"可以"。
所以保护只是给子 agent 的 GitHub 兜底。更宽的检查是 PM 自己的判断:上面第 15 步
「合并与清理」里,它还会读 .gitlab-ci.yml、.circleci/config.yml、Jenkinsfile
和 azure-pipelines.yml(存在的话),并在推 main 之前把读到的结果告诉你。
安装
dsh plugin --profile tui add dsh-crew # 或 --profile web
然后重启 dsh。启动时会把 crew 预设写入 $DSH_HOME/.agent-presets/crew(如果那里
已有别人写的 crew 文件夹,则原样保留)。要用角色,请把会话切到 Crew 预设。
不启动 dsh 也可以自检:
npm test # git 保护规则、插件挂载、所有 QA 用例,最后是 Verdicts 门;不需要 dsh
本仓库自己的 CI 会在每次推送时跑 npm test,只有推 v* 标签才会发布。推标签那一次
还会建 GitHub release,文字取自 CHANGELOG.md 里对应版本那一节。这段文字在
npm publish 之前就取出来——所以没写这一节的版本会让整次运行停下,而不是发出去。
只有真的发布了,才会建 release。有一个缺口值得知道:在没有装 @deepseek-ai/dsh-tool-subagent 的机器上,tools/verify-mount.mjs
会跳过角色工具那一半检查,而 CI 就是这样的机器。它会出声说明跳过了哪一半,所以一次
全绿的意思是"公共运行器能检查的都检查了",不是"全都检查了"。
npm test 最后跑的那一道,管的是团队自己的记录。node tools/verify-tasks.mjs 读
docs/design/tasks.md——那里每个任务小节都带一条 Verdicts 行,也就是 PM 对四道
评审(code、security、qa、doc)的报告。下面几种情况它会变红:
- 某个任务小节没有
- **Verdicts**:行,或者有不止一条; - 四个值里缺任何一个;
- 某个值写着
not run或skipped,但破折号后面没有它自己的理由; - 某个值写着
changes needed,却没有点名哪个任务号去修。
每次跑它都会把总数大声打出来:还有多少个值是 not run,多少个是 skipped。
通过不等于干净。 那一行是 PM 写的,而评审员按设计不能写文件。所以这道门能证明的是
这一行被写下来了、每次跳过都留了一句理由;它不能证明评审真的跑过——PM 直接写
code: pass 也能过,而且任何自动检查都补不了这个洞。它之所以存在:本仓库自己那次作业
里,PM 跳过了大约 20 个任务的代码评审、以及这次作业大部分的文档评审,而当时什么都没有
变红,是用户开口问才发现的。这道门堵不住这件事,它只是让下一次这样的跳过当天就看得见,
而不是二十个任务之后才看得见。
配置
全部可选,各自放在所属的平面。
PM 与 git 保护——你 profile 的 cordis.patch.yml 里 dsh-crew-core 和
dsh-crew-git-guard 两行:
| 配置 | 默认值 | 作用 |
|---|---|---|
rolesDir |
~/.dsh/crew/roles |
用同名文件替换自带的角色人设 |
limits.liveAgents |
20 |
同时活跃的团队 agent 数 |
limits.reviewRounds |
3 |
评审轮次上限,超过就交给你决定 |
installPreset |
true |
是否把 crew 预设写入 $DSH_HOME/.agent-presets |
jobsDir |
~/.dsh/crew/jobs |
作业状态存放位置,也是中断提示读取的位置 |
resumeNotice |
true |
会话开始时把未完成的作业摆到 PM 面前 |
enabled(保护) |
true |
关闭 git 保护——不建议 |
trustRootAgent(保护) |
true |
信任你自己的会话(PM)执行任意 git 或发布命令 |
approvalFile |
~/.dsh/crew/push-ok |
一次性推送审批文件。必须是文件路径,不能是文件夹——结尾带斜线会在启动时报错 |
角色——~/.dsh/.agent-presets/crew/agent.cordis.yml 里的 dsh-crew-roles 一行:
| 配置 | 默认值 | 作用 |
|---|---|---|
rolesDir |
~/.dsh/crew/roles |
同样的人设覆盖目录 |
roleAllow |
评审:read, glob, grep;调研:read, glob, grep, write, web_search |
该角色只能用这些,其余一律关闭 |
roleDeny |
架构师、工程师、测试工程师、代码工程师、QA:crew 工具 | 该角色除这些外都能用 |
roleModels |
会话模型 | 按角色指定 provider 和 model |
roleAllow、roleDeny 和 roleModels 里的键,是这个角色的工具名去掉 crew_ 前缀——
researcher、architect、engineer、test_engineer、code_engineer、qa、
code_reviewer、security_reviewer、doc_reviewer。那个文件自己的注释里列了全部九个。
你写在那里的名单必须至少点出一个工具,而且必须是一个列表。 空列表会让 dsh-crew
拒绝启动;空字符串、0、false、{},以及任何不是"工具名列表"的值,同样会
拒绝启动,报错信息里会点名是哪个字段、哪个角色键。早先的版本会安静地吃掉这种值、
把你写的那一行丢掉;而当它是那个角色唯一的名单时,那个子 agent
就一条过滤规则都没有了——一个本该只读的评审拿到了这个预设注册的全部工具,
bash、write、edit 都在里面,而且没有任何提示。
"不加限制"这件事没有写法:要放宽一个角色,就把它可以用的工具列出来。
想回到出厂名单,就删掉那一行,或者把它设成什么都不写(YAML 里一个裸的 ~),那仍然
表示"用出厂名单"。这一条落在哪个版本,见 CHANGELOG.md。
许可
MIT
原始 README: https://github.com/stuarthu/dsh-crew/blob/main/README-zh.md ↗
同类插件
查看全部 →
deepseek-harness
从仓库或系统描述生成经过校验的自包含交互式架构图、流程图、时序图、数据流图和生命周期图。

dsh-plugin
通过 DSH MCP 客户端挂载 Ouroboros 的纯配置包,在 DSH 中提供 36 个涵盖需求访谈、Seed、执行、评估与演化流程的工具。

dsh-tongflow
基于 TongFlow 的“片场”插件,用于图片、配音、音乐与视频制作:agent 为每个资产生成 TongFlow 工作流文件(.tongflow.json)并通过 TongFlow 插件执行,内嵌工作流画布,按镜头/角色/take 组织项目,附漫剧模板;以 @tongflow 开头的会话进入 Studio 界面。

helloagents
AI 编码 CLI 的工作流层:技能、项目知识、交付检查、更安全的配置写入与可恢复执行

dsh-ai-novel-writer
安装专用 AI 小说创作预设与工作台:提供带修订号的本地项目资产、紧凑侧边工作台,以及需要原生审批的逐文件变更。

rea
用 agent 逆向任何东西:从应用行为到原生二进制