适合人群:已经在用 DeepSeek Harness(DSH)跑真实项目,却发现自己慢慢变成了「人肉调度员」的开发者------一边盯着 AI 写代码,一边在脑子里排优先级、记谁卡住了、催谁继续。
阅读收获:看懂「协调者 / 子会话 / 台账」这套结构;装好就能派出第一个任务;带走一份「目标怎么写、依赖怎么排、什么时候别用它」的避坑清单。
一、为什么你需要了解它?(WHY)
一个会话干所有活,会撞上三堵墙
你有没有遇到过这种场景:
早上九点,你打开一个 AI 编程会话,信心满满地说:「帮我做完这个功能------改接口、加页面、补测试、顺手把文档更新一下。」
AI 很配合,一路干到下午。你的会话窗口里,聊天记录已经长到需要压缩,中间夹着三段日志、两份报错、一次需求变更。然后它开始忘事:上午定好的字段名,下一轮又拼错了;原来的方案,下午悄悄变成了另一个样子。😭
这不是模型不够聪明,而是一个会话同时承担了太多角色:它既是产品经理,又是执行者,还是自己的验收人。三堵墙就摆在面前:
- 上下文墙:需求、日志、代码、报错全堆在一个上下文里,压缩之后最先丢的,往往是最重要的那条约束。
- 串行墙:一次只能推进一件事。你在等 A 的时候,B 和 C 只能排队------哪怕它们彼此毫不相干。
- 失忆墙:新开一个会话,前一天踩过的坑、定过的决策,全都归零。
真正的瓶颈不是「写」,是「调度」
我们早就过了「AI 写不出代码」这道坎。现在更常见的困境是:活太多,而你只有一个会话,而且你还得亲自当调度。
你在脑子里维护着一张表:哪个任务在跑、哪个卡住了、哪个要先等接口改完、哪个产物放在哪个目录。这张表一复杂,人就开始出错------忘了催、忘了看、忘了收产物。
而这张表本该是机器的事。
dsh-orchestrator 的解法
dsh-orchestrator 是 DeepSeek Harness 的一个插件(npm:@wjj865-code/dsh-orchestrator,当前 0.11.0,MIT)。它干的事很朴素:把你脑子里那张表,变成一份真的台账;把「拆任务、派任务、盯任务、收产物」这四件事,交给你自己的一个个子会话。
一句话总结:它不让你写得更快,它让你能同时开工的地方更多,而且不用你亲自记着。
二、它到底是什么?(WHAT)
一句话定义
一个「主会话 + 若干子会话」的编排层:主会话只负责拆任务和收回报,子会话各自在目标目录里干活,中间的一切状态(谁在跑、依赖谁、产物在哪、谁在等你拍板)都落在一份 SQLite 台账里。
三个角色,各管一摊
| 角色 | 是谁 | 能干什么 | 不能干什么 |
|---|---|---|---|
| 协调者 | 固定的那个「主会话」 | 拆任务、派子会话、看回报、用 present 声明交付产物 |
读写文件、跑命令------执行层有 guard 硬拦 read/write/bash 等工具 |
| 子任务 | 独立的 DSH 子会话,在指定目标目录运行 | 完整的文件 / shell / 搜索 / 网页 / todo / skill 工具;可只读、可续派、可停止、可重派 | 不能替别的任务说话(task_report 只对自己那一条任务生效) |
| 台账 | $DSH_HOME/state/orchestrator/tasks.sqlite |
记状态、依赖、分组、写域、消息、产物、目标镜像;重启后账还在 | 不会自动恢复损坏的快照(只给路径提示) |
这个分工的关键在于协调者被故意削弱了:它没有文件系统权限,所以「主会话手滑改了你的代码」这种事从根上不会发生;它只动嘴、只派活、只看文档。真正的手,在子会话那一边。
一次派发,走完这条流水线
text
你说需求
→ 协调者 task_create():goal / acceptance / constraints 落库,生成任务编号 t-xxxxxxxx
→ 有 dependsOn 且前置没完成? 状态 waiting_dep,不派发、不占并发位
→ 前置全部 done? 自动晋升 pending,按 priority 排队
→ 派发:子会话拿到【任务编号】+「先调 task_detail 读详情」两行指引
→ 子会话 task_detail 自助读取:目标全文、验收、约束、背景、用户原话、
工作目录与权限、前置任务摘要、经验文件、已有产物
→ 干活中:task_report progress / blocked / needs_auth(可带 question + options)
→ 收场:done → 结算为已完成;被中断 → 已取消;异常 → 失败;
只报过进展就没了 → 会话丢失(等你在收件箱里拍板)
→ 收产物:done 时自动从工作区改动里收集文件,与显式声明的产物按路径去重合并
→ 沉淀:完成摘要进日志层只归档;被蒸馏成原子事实的经验/决策/偏好,下次派发时注入
注意其中一句:brief 只有两行指引 。这是 0.11.0 的一个刻意设计------目标正文不重复塞进提示词,子会话自己用 task_detail 去读创建时的快照。为什么这么改?后面「演进史」那节会讲,简单说:提示词塞得越满,子会话开局的注意力越贵。
12 个工具:10 个给协调者,2 个给子会话
| 归属 | 工具 | 作用 |
|---|---|---|
| 协调者 | task_create |
建任务并派发;支持 dependsOn / group / writeScopes / priority / readOnly |
| 协调者 | task_list |
活跃 / 历史任务;按 group 看组进度 |
| 协调者 | task_status |
单任务详情:依赖、写域、分组、优先级、问题、最近消息;include: 'full' 看子会话最后一条回复全文 |
| 协调者 | task_message |
续派消息(改目标请走它) |
| 协调者 | task_stop |
停止运行中 / 排队 / 等前置的任务 |
| 协调者 | task_retry |
重建 failed / orphaned 的任务,保留编排字段 |
| 协调者 | task_inbox |
收件箱:待你决定、失败/丢失、长时间没动静 |
| 协调者 | history_search |
查插件记忆、历史任务、平台历史会话 |
| 协调者 | project_context |
只读项目白名单文件、顶层结构、最近任务、相关记忆 |
| 协调者 | memory_note |
记一条跨任务有效的决策 / 偏好 / 经验(写入即蒸馏成原子事实) |
| 子任务 | task_report |
progress / done / blocked / needs_auth,可带 question / options / artifacts |
| 子任务 | task_detail |
只读自己任务的创建详情;越权会被拒绝 |
12 个工具全局注册,但权限切得很干净:task_retry / task_inbox / history_search / project_context / memory_note 仅主会话可调,task_detail / task_report 只对任务自己的子会话开放。
和「多开几个窗口手动复制粘贴」有什么不同
| 维度 | 手动多开窗口 | dsh-orchestrator |
|---|---|---|
| 上下文 | 你自己在窗口间搬运背景,越搬越乱 | 每条子任务独立上下文,背景按需现读 |
| 状态 | 靠脑子记、靠标签页 | 台账落库,task_list / 面板随时看 |
| 依赖 | 靠你自己盯着 A 做完再开 B | dependsOn 声明,完成自动晋升,失败沿链传播 |
| 失败 | 你发现它没动静,然后从头再来 | 结算自动收场 + 启动对账兜底,可直接 task_retry |
| 产物 | 自己找、自己拷 | done 自动收集,产物库按项目 / 日期分组 |
| 经验 | 下次重新踩一遍 | 蒸馏成原子事实,注入后续任务 |
三、怎么用?(HOW)
3.1 装它:一行包名 + 一次彻底重启
-
DSH → 插件 → 添加插件,粘贴:
text@wjj865-code/dsh-orchestrator@0.11.0 -
彻底退出 DSH 再启动(只关窗口不生效)。
-
纯 dsh 表面左侧栏「插件」下方会出现「◈ 主会话」;Bot 模式用会话头「任务」按钮进面板。
两个入口的分工要记住:侧栏「主会话」始终可见 (没建主会话时靠它引导创建),会话头「☰ 任务」只在主会话显示------切到子会话或其他会话就自动隐藏。
装完发现是旧版? 大概率是镜像还没同步。
npm publish只写registry.npmjs.org,而国内默认走的registry.npmmirror.com是另一套库,回源要几十分钟到几小时。维护者侧有npm run mirror:check(只看不动)和npm run mirror:sync(触发同步并轮询)两个脚本兜这件事。还有一个隐蔽的坑:
npm view --registry=https://registry.npmmirror.com会命中本地 npm 缓存,显示旧latest甚至404。怀疑时加--prefer-online再查,或直接看 HTTP 响应。
3.2 派第一个任务:把「验收标准」写进去
用自然语言对主会话说需求即可,协调者会把它翻译成 task_create:
js
task_create({
title: '修复空数据文案',
goal: '空窗口时报表给出明确说明而不是空表',
constraints: ['只改 src/report.js'],
acceptance: ['空窗口输出「该窗口没有调用记录」', 'npm test 全绿'],
user_verbatim: '你看这个空数据啥也没显示',
})
这四个字段值得逐个说清楚,因为任务质量九成取决于它们:
goal:要达成的结果,不是操作步骤。写「空窗口要有说明」而不是「打开文件、找到第 42 行、加一个 if」。acceptance:怎么算完成,可判定的句子。「测试全绿」比「代码质量好」有用一百倍。constraints:别碰什么。「只改 src/report.js」这类边界,比后面十次返工都便宜。user_verbatim:用户原话。它是子会话理解语境的最后一道保险,而且这条不会写进日志。
3.3 依赖、分组、写域、优先级:把「排队」变成声明式
js
// 先建 A,再建依赖 A 的 B
task_create({ title: '改接口', goal: '...', acceptance: ['...'], group: '登录改造' })
task_create({
title: '联调登录',
goal: '...',
acceptance: ['...'],
dependsOn: ['t-xxxxxxxx'],
group: '登录改造',
writeScopes: ['src/auth'],
priority: 5,
})
四条规则记住就够了:
- 前置没完成:B 是
waiting_dep,不派发、不占并发位------不会白白消耗一个子会话名额。 - 前置全部
done:B 自动变pending,用task_detail能看到前置任务的摘要和产物(按当前台账现读,不是创建时的快照)。 - 前置失败:B 自动
failed,错误里写明原因。 writeScopes只做冲突提示 ,不是文件锁;priority高的先派。
3.4 你不盯着,它也不会装死
这是新用户最容易低估的一层:
| 机制 | 默认 | 作用 |
|---|---|---|
收件箱 task_inbox / 面板「待你决定」 |
开 | 集中看待确认、要授权、失败、长时间没动静的任务 |
blocked / needs_auth 带 question + options |
按需 | 子会话卡住时,把问题和一个选择题一起递到你面前 |
| 结算收场(S1) | 开 | 子会话被中断 → cancelled、异常 → failed、只报过进展就结束 → orphaned,不再让任务悄悄死在 running 里 |
| 启动对账(S2) | 开 | 插件启动时扫台账,把「running 但子会话已不存在」的任务标 orphaned,兜住进程崩溃 |
| 空闲看门狗 | 开 | 每 5 分钟巡检,running 的任务 15 分钟无心跳/消息/状态变化就告警一次------只告警,不改状态、不 kill、不重派 |
日报 digestAt |
关 | 到点汇总完成 / 在跑 / 待决定 / 失败 / 产物,比如 '09:00' + Asia/Shanghai |
关于看门狗那句「只告警」,值得单独强调:它是观察者,不是执行者。你睡了一觉回来,它会告诉你「这三个任务没动静了」,但不会替你把它们杀掉------判断权始终在你手上。
3.5 任务面板:DeepSeek 风格的三段式
会话头点「☰ 任务」,会打开一个面板。它不是一张表格,而是三段式布局:
| 段 | 内容 |
|---|---|
| 顶部命令栏 | 标题 + 主会话状态点、任务搜索框、刷新、打开主会话;下面 4 张指标卡(进行中 / 待你决定 / 已完成 / 失败,点一下等于切筛选) |
| 中部工作区 | 左侧筛选轨(带计数徽标)+ 任务卡列表;切到「产物」变成产物库(按项目 / 日期分组) |
| 右侧详情抽屉 | 点任务卡或「详情」从右侧滑入,遮罩、Esc、关闭按钮都能收起 |
抽屉里有 7 个可折叠区块:概览、最新回复 (子会话最后一条 assistant 消息)、Brief (验收 / 约束 / 背景 / 用户原话)、目标状态 (只读)、消息 (待发 / 已投递 / 投递失败)、依赖 (上下游)、产物(复制路径);底部动作条是停止(内联二次确认)/ 重派 / 追加消息 / 打开子会话。
配色细节值得一提:颜色、圆角、阴影、动效全部取宿主的设计 token(--dsw-* 那套),所以深浅色主题自动跟随 ,面板自己不写死任何颜色。顺手修掉的一个老 bug 也很典型------旧代码用的 --dsw-alias-color-* 在宿主 token 表里根本不存在,一直走 fallback,深色模式下就是脏的。
3.6 几个值得专门开的高级开关
| 开关 | 默认 | 什么时候打开 |
|---|---|---|
readOnly: true |
否 | 「先侦察、别动手」的任务:读代码、摸清结构、给结论。只认显式 true |
keepAlive: true |
关 | 希望子会话在回合边界接着跑 而不是先 idle 再结算;keepAliveMode 可选 steer(同回合多走一步)/ followup(新回合),keepAliveMaxRounds 默认 8 轮(1--50) |
memoryInject / memoryLimit |
开 / 3 条 | 新任务注入命中的事实层记忆(lesson/decision/preference/note);日志层不进注入 |
workerPreset: orch-debug |
dsh-orchestrator-worker |
想让子任务跑在「创造模式」能力栈上(cordis_* 只读洞察工具 + dsh-doc 技能 + plugin_manager)调试插件时 |
staleMinutes / stallRounds |
30 / 3 | 调整「多久算陈旧」和「连续几轮 progress 后提醒可能卡住」 |
keepAlive 的边界要写清楚,否则很容易被误解:它只 对「台账 running + 平台 goal 未完成」的任务生效,停止条件包括轮满、goal 已收口、任务离开 running、回合被中断、只读任务、收件箱里已排了下一轮。它是宿主替子会话开工(每轮一次完整模型调用),不是成本控制,也不能阻止显式 stop 或模型报错。
四、它是怎么长出来的?(0.0.0 → 0.11.0)
插件不是一次设计出来的,是被真实使用逼出来的。按 CHANGELOG 的版本主题看,主线非常清楚:
| 版本 | 主题 | 一句话 |
|---|---|---|
| 0.0.0-stage | npm 占位(已 deprecate) | 先占个名字 |
| 0.2.x | 入口直达主会话、只读子任务 | 「能用」的最小事 |
| 0.3.x | 发布流水线(provenance + 重复发布保护)、MIT LICENSE | 敢往外发了 |
| 0.4.0 | 依赖编排 :dependsOn / group / writeScopes / priority |
从「能派任务」变成「能编排」 |
| 0.5.0 | 收件箱 / 问题选项 / stallRounds / 日报 |
让卡点主动浮出来 |
| 0.6.x | 产物库与面板操作台;拆出 ARCHITECTURE / LIMITATIONS 文档 | 面板成为主入口 |
| 0.7.0 | 面板动作错误码与幂等、详情抽屉、done 自动收集产物 | 操作要可解释、可重放 |
| 0.8.0 | 结算收场 S1 + 启动对账 S2 + 调试预设 | 不让任务悄悄死在 running |
| 0.9.0 | 记忆分层 M1 + 空闲看门狗 W1 + 子会话目标 G1 | 上下文是最贵的资源 |
| 0.10.0 | 子会话读目标 G2、最新回复 B1、brief 瘦身 L1、协调者交付产物 P1 | 提示词瘦身,能力补齐 |
| 0.11.0 | 回合间 keep-alive K2 + task_detail T1 + 入口仅主会话 U1 + 面板 UI 重做 U2 |
从「塞提示词」转向「让子会话自己查」 |
几个转折点值得单独讲,因为它们解释了现在为什么长这样。
转折一:从「能派任务」到「能编排」
最早的版本只能派发独立任务,本质是「批量开工」。0.4.0 加上 dependsOn 之后,才有了依赖图 这件事:前置没完成的任务停在 waiting_dep、不占并发位,前置一完成自动晋升,前置失败则沿链传播。这也是「编排」这个词第一次名副其实。
转折二:最大的坑不是失败,是「沉默」
用久了会发现,真正让人抓狂的不是子会话报错------报错至少是个明确的信号。真正麻烦的是它什么都没说,就那么停在 running 里。你去睡觉,第二天回来一看:状态还在运行,会话早就没了。
0.8.0 的 S1(结算原因闭环)和 S2(启动对账)就是冲这个来的:消费子会话结束的 stopReason,把 running 推到终态(正常完成 → done、被中断 → cancelled、异常 → failed、只报过进展 → orphaned);插件重启时再扫一遍台账,把「running 但子会话已经不活跃」的任务标成 orphaned。
一句话:宁可给你一个「会话丢失」待你决定,也不要让它假装还在跑。
转折三:记忆不是越多越好
0.9.0 做了一件反直觉的事:把 done 的摘要从记忆里「降级」了 。完成摘要不再写进 task-done 这种会注入记忆的层,而是进日志层 task-log------只归档、不注入 brief。同时给写入加了蒸馏:整块丢弃代码围栏、shell/测试输出、引用原话、纯路径清单,事实层单条上限 160 字符。
只有真的带「结论 / 经验 / 注意 / 避免」这类标记的分句,才会被蒸馏成 lesson 原子事实,和 decision / preference / note 一起注入后续任务(默认 3 条)。
因为上下文是最贵的资源。 一条「这次改完了」的流水账对下一个任务毫无价值,但它会实打实占掉提示词预算。
转折四:brief 越写越短,子会话越来越自主
0.10.0 把目标挪给了平台 goal(子会话用 get_goal 读,brief 只留一句指引 + 160 字兜底),把经验挪进了 lessons/ 记忆文件(brief 只留条数、摘要和路径)。0.11.0 更进一步:brief 只剩两行------
text
【任务编号】t-xxxxxxxx
【任务详情】先调 task_detail 读取完整任务详情......
目标全文、验收、约束、背景、用户原话、工作目录、前置任务、经验、产物,全部由子会话自己 task_detail 现读。好处很直接:提示词长度与任务复杂度、记忆条数彻底解耦,派发不再因为「塞了太多背景」而变贵;前置任务的进展也不再是创建那一刻的快照,而是派发时的实时台账。
代价也有:task_detail 必须是只读的、必须做越权校验(exec.agent.id 或 exec.agent.session.id 必须等于台账里那条任务的 session_id,否则返回「读取被拒绝」,主会话查任务请用 task_status)。
转折五:能力越强,越要默认关
0.11.0 的 keepAlive 是最能说明「设计克制」的一处:它能让子会话在回合边界接着跑,理论上是个很强的自动化能力。但作者给它的默认值是关,而且:
- 只对「
running+ 平台 goal 未完成」的任务注入; - 轮数预算落台账(默认 8 轮),每次进入
running归零,重复/并发事件用条件UPDATE原子加轮,不超发; - 与结算去重:不改状态、不写
progress_count、不写followup消息、不触发结算; - 监听器全量
try/catch,绝不把异常抛回派发链路。
同样克制的还有目标状态:任务派发时把 goal 同步成平台 goal,然后立即 disarmed------goal 只是持久目标状态,不会驱动子会话自动续跑。
顺带一个冷知识:0.8.0、0.9.0、0.10.0、0.11.0 都是 2026-11-15 发的 ,四个 minor 在同一天落地,0.11.0 的 npm test 跑完是 602 条全绿。
五、最佳实践
✅ 1. 目标写「验收」,不写「步骤」 goal 写结果,acceptance 写判定句。把步骤留给子会话------它比你更清楚自己这一轮要怎么走。
✅ 2. 一个任务一个写域 writeScopes 虽然只是提示,但它会帮你在派发前想清楚「这两个任务会不会打架」。真想避免冲突,靠的是拆分,不是靠声明。
✅ 3. 用 group 组织,别用编号 「登录改造」比「第 3 批」有用得多:组进度可以直接看,人的记忆也挂得住。
✅ 4. 别在目标里复述背景 经验、前置任务、已有产物都走 task_detail 现读。目标里复述一遍,既费提示词,又容易和实际状态不一致。
✅ 5. 把把关点做成选择题 卡住时用 blocked 带 question + options,让它出现在通知、详情和面板里------比在一条长消息里藏一句「请问这里要不要改」有效得多。
✅ 6. 侦察任务先上 readOnly 「先别动手,告诉我这块代码是怎么组织的」这类任务,用 readOnly: true,子会话进只读沙箱,物理上杜绝顺手改坏。
✅ 7. 产物写绝对路径 子会话的工作目录不是你的目录。present 交付时写绝对路径,才点得开。
✅ 8. 每天扫一眼收件箱 尤其是挂了日报的:完成 / 在跑 / 待决定 / 失败 / 产物一屏看完,比逐个点任务卡快得多。
六、常见误区与避坑指南
❌ 误区一:把它当「更快的 AI」
它不加速单次推理。它省的是你的调度成本 和单个会话的上下文压力 。每个子会话都是一次完整的模型调用,keepAlive 更是宿主替子会话连续开工------能力上很爽,账单上要自觉。
❌ 误区二:目标写成一篇需求文档
目标越长,子会话开局可用于「理解任务结构」的注意力越少。好的 goal 通常一两句:要什么、不要什么。背景放 constraints 和 refs,别全塞 goal。
❌ 误区三:以为 writeScopes 是文件锁
它只提示冲突 ,不是锁。两个任务同时盯上 src/auth 也不会互相阻塞,该串行请用 dependsOn。
❌ 误区四:依赖建完还想改
dependsOn 创建后不可编辑,也不允许指向未来的 id。所以拆任务时想清楚:不确定就先拆小、串成链,别指望事后调整。
❌ 误区五:装完没彻底重启,就说「插件坏了」
只关窗口不生效,必须彻底退出 DSH 再启动。另外「装到的是旧版」九成是镜像没同步------先 mirror:check,再怀疑插件。
❌ 误区六:在子会话里找任务面板
入口只在主会话显示,而且判定信号不可用时默认隐藏(fail-closed)。这不是 bug,是刻意的:宁可少一个入口,也不在子会话里露出一个会误导你的面板。
❌ 误区七:指望它替你控制成本
keepAlive 默认关,有轮数上限,注释里写得很直白:它是宿主替子会话开工,不是成本控制。想省钱,关掉它、缩短目标、减少并发,比调任何参数都直接。
❌ 误区八:以为日志会替你记住一切
goal / summary 在日志里只留 80 字,user_verbatim 不写日志。想让某个决定被记住,用 memory_note 显式记一条原子事实------写日志和写记忆是两件事。
七、它不适合什么场景(诚实说)
- 一句话能改完的小改动:拆任务、建台账、写 brief 的处理开销大于收益,直接说就行。
- 需要强实时协同的编辑 :两个人(或两个子会话)同时改同一批文件,
writeScopes帮不了你。 - 想让它自动无限续跑 :
keepAlive默认关、有轮数预算,平台 goal 创建即 disarm。这是设计取向,不是待修 bug。 - 需要跨机器或多人共享状态 :台账是本机
$DSH_HOME/state/orchestrator/下的一份 SQLite,插件不做多机同步------它是你一个人的记账本。 - 完全无人工在场的流水线 :它明确把判断权留给人------
blocked/needs_auth/orphaned都在等你看一眼。想看「它自己跑完全程」,那它不是这个工具。
八、写在最后
这篇文章本身就是一次派发。
主会话建了「公众号推广」组里的一个子任务(编号 t-5a95ac12),只给了三样东西:目标(写一篇介绍和推广这个插件的文章)、五条验收标准、几份背景资料的路径。然后一个子会话在指定工作目录里读素材、提炼过往 17 篇文章的风格、成稿、跑自检、最后用 task_report done 把产物交代清楚。
中间没有人催,也没有人搬上下文------因为该记的都在台账里。
回到最开始那三堵墙:上下文墙靠「任务级隔离 + 按需现读」拆掉,串行墙靠「多条子会话并行」绕过,失忆墙靠「蒸馏后的原子事实注入」补上。这套东西没有任何魔法,它的价值就在于把一件本该由流程负责的事,从你的脑子里搬进了数据库。
装它不需要改你的工作方式:你照旧用自然语言说话,只是从今天起,你可以放心地一次说三件事,然后去喝杯咖啡。☕
参考链接
- npm:
@wjj865-code/dsh-orchestrator - 仓库:github.com/wjj865-code...
- 变更记录:仓库根目录
CHANGELOG.md(0.0.0-stage → 0.11.0) - 架构与边界:仓库
docs/ARCHITECTURE.md、docs/LIMITATIONS.md、docs/UI.md、docs/DEBUG-PRESET.md - License:MIT