DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

hu568 /

hu568/dsh-plugin-cluster-preset

Verified

DSH「集群模式」agent preset:主控智能体自主编排 5 个具名专家子智能体,附「智能体寿命论」阶段提示器

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@421f57c0

集群模式(cluster)——自主编排子智能体的 DSH Agent 预设

模仿 oh-my-openagent 的 Sisyphus 编排者工作方式, 让一个主控智能体自主把工作分派给一组专门子智能体,并并行扇出、回收验证。

这个预设长什么样

一个 @deepseek-ai/dsh-agent-preset 声明,config.plugins 就是那个 Agent 的完整组成部分:

组成 说明
编排者人设 意图门 → 委派检查 → 并行扇出 → 验证 → 失败恢复的整套工作法
5 个专家委派工具 explore / librarian / oracle / metis / momus,每个一个具名工具
2 个通用委派工具 subagent(全新上下文)/ subagent_fork(继承本会话)
寿命论阶段提示器 cluster-preset/lifecycle-reminder.js:按理论上下文占用折算年龄,进入四个壮年阶段时各提示一次
编排者自带的工具 与出厂 standard 预设同款:文件、搜索、终端、todo、web、skill…

专家名册

工具 角色 成本 何时用
explore 代码库检索专家 低 「X 在哪实现」「哪个文件含 Y」
librarian 外部参考检索专家 中 陌生库、官方文档、第三方实现
oracle 只读高智顾问 高 硬核调试、架构决策、方案取舍
metis 规划前顾问 高 需求含糊、任务复杂——先厘清意图与边界
momus 计划评审员 高 计划写完后评审其可执行性
subagent 通用委派(全新上下文) 中 实现类工作,无对口专家
subagent_fork 通用委派(继承本会话) 中 实现类工作,且需要当前会话上下文

寿命论阶段提示器

(源于同目录的 智能体寿命论.md)

把会话的理论上下文占用当成年龄 —— 文档的刻度是 年龄 = 占用百分比,1% = 1 岁:

阶段 理论年龄 文档判词 提示的行为要点
1/4 20 岁 轻量任务可以完成 趁窗口干净,把关键结论就地固化
2/4 30 岁 大部分任务都能完成(轻微、可观察到的降智) 细节别凭记忆,重新读;切小单元再委派
3/4 40 岁 降智明显、行为固化、思想或已僵化 停止在同一思路加码;关键结论做独立复核
4/4 60 岁 只有少数复杂任务需要这么长的上下文 尽快收尾,别开新战线;固化已有产出

每个阶段只提示一次,记录在会话投影 clusterLifecycle 里 —— 用投影而非内存标记, 因为它从会话日志折叠而来,上下文压缩也抹不掉(否则「返老还童」后会重复唠叨)。 年龄一步跳过多个阶段时,只按当下最高的那档提示,不会把低位阶段排队补播。

两把尺子

文档区分「理论寿命」(标称上限,决定智力)与「真实寿命」(实际生效窗口,决定生命)。 提示器同时报告两者,于是那个错位看得见:

当前刻度(理论年龄 30.5 岁,生命进度 100%):
  理论年龄 30.5 岁(占用 30.5%)
  理论寿命 1.00M / 真实寿命 300.0k → **生命进度 100%,快于智力**
  ⇒ 生命已走到尽头,而智力年龄才 30 岁。**它不是老死的,是在最能干的时候死的。**
  • 真实寿命取 token meter 记录的 contextWindow(本会话实际生效的那个)
  • 理论寿命默认与真实寿命相同,四个阶段因此都可达;想复现文档里 1M / 300k 的错位, 把 theoreticalWindow 显式设成标称值即可

配置

在 build-preset.mjs 的 cluster-lifecycle-reminder 行加 config 即可:

- id: cluster-lifecycle-reminder
  name: ./cluster-preset/lifecycle-reminder.js
  config:
    subjectPreset: cluster        # 只对本预设生效;null = 所有会话
    includeSubagents: true        # 子智能体也提示(它们才是真吃上下文的)
    explain: true                 # 在系统提示里加一段说明 + 实时年龄
    theoreticalWindow: 1000000    # 标称上限;省略则用实际生效窗口
    stages:                       # 省略则用内置四档
      - age: 20
        label: 轻量任务可以完成
        lines:
          - 【寿命论 · 壮年 1/4】理论年龄 20 岁。
          - ''
          - 趁窗口还干净,把关键结论固化下来。

任何非法值都会让挂载失败并给出具体原因(fail-loud),而不是被静默忽略。

它怎么读数据

数据来自 @deepseek-ai/dsh-token-meter 的两处会话投影: contextPressure.pressureTokens(提示侧压力 = 输入 + 缓存流量,不含输出)与 contextPressure.contextWindow(路由模型的实际窗口)。

注入走 agent/pre-step,产出一条 source.form: 'notice' 的 user 消息 —— 与 dsh-time-context / dsh-plan-mode 同一范式(那两者是这套机制的现成范例)。

零运行时依赖:只 import node:crypto,与宿主服务的交互全经传入的 cordis ctx, 不 import 任何 @deepseek-ai/*,因此不受解析域影响。

两条叶子约束

每个专家都是叶子——不能再往下委派。这由两道独立防线保证:

  1. maxDepth: 1。子智能体运行时校验的是被创建的子体深度: childDepth = delegationDepthOf(parent) + 1,当 childDepth > maxDepth 时拒绝。 编排者处于深度 0,所以它创建的子体深度为 1 —— maxDepth: 1 恰好放行编排者自己的调用; 而身处深度 1 的专家若要再委派,其子体深度为 2,会被拒绝。

    ⚠️ 这里不能写 maxDepth: 0。那个校验跑在被创建的子体上,不是调用者身上, 所以 0 会连编排者自己的委派一起挡掉。这是个容易踩反的语义。

  2. toolFilter(第二道防线)。每个专家通过 deny 移除了全部委派工具 (subagent / subagent_fork / explore / librarian / oracle / metis / momus) 以及 write / edit。选 deny 而非 allow,是为了让专家保留 read / grep / glob / pwsh / web_* 等常规作业工具,不必在此逐条重列。

    ⚠️ 通用委派行不能设 modelSelectionSettings: true。(曾经踩过,整条专家线因此全挂。)

    tools.restrict() 能移除的只有调用作用域继承到的名字;它的合法名集合是 「全局层 + 作用域链上的祖先层」,永远不含调用作用域自己那一层 (dsh-tools/lib/index.js:2937-2958)。

    而 modelSelectionSettings: true 会把该行的注册从挂载时挪到每个 Agent 自己的层—— dsh-tool-subagent 不再于挂载时注册,而是等 Agent 创建后通过 candidate.ctx.inject(...) 注册进那个 Agent 自己的层 (dsh-tool-subagent/lib/index.js:610-659)。两个后果都会咬人:

    1. 把名字写进 deny 会在子体创建之前就抛错(不是拼写问题):
      tools.restrict() names unknown global tool "subagent";
      known global tools: …, explore, …, subagent_fork, …
      
      注意这条消息的不对称——subagent_fork(挂载时注册 ⇒ 祖先层)在可过滤名单里, subagent 不在。这正是当初那个 bug 的指纹。
    2. 更麻烦的是,每个被委派出去的子体都会自持一份该工具(深度 1 的专家也不例外), 而「自己的层」对限制是豁免的 ⇒ 任何过滤器都拿不走它。所以专家只要拿到 subagent,就能继续往下委派,「叶子」承诺当场作废。

    把该标志关掉(本预设的做法),注册就留在挂载时的祖先层:deny 生效, 专家干脆看不到任何委派工具。代价是失去通用委派工具的子模型选择 (provider/model/reasoning_effort 参数与 list_subagent_models)。 若日后要重新开启,必须同时把 subagent 从所有 deny 名单里拿掉, 并接受叶子保证退化为只靠 maxDepth 兜底 —— scripts/validate.mjs 会在 「设了标志却又写了名字」时直接判定 INVALID。

    过滤名单与 maxDepth 都在创建子智能体时校验(而非加载时),所以拼错名字会在 第一次委派时炸出来。scripts/validate.mjs 会先把每个名字与真实可过滤集合对齐, 并从预设里推导哪些名字是 per-agent(不可过滤)的,提前拦截。 scripts/test-restrict-scope.mjs 用真实 ToolRuntime + 真实 restrict() 跑两遍:① 发布产物的每份 deny 名单都必须通过,且子体确实拿不到那些工具; ② 合成当初的 per-agent 形状,断言它仍然复现原报错串(防止约束被遗忘)。

安装

方式零:作为 bundle 安装(推荐)

本仓库是一个可分发的 DSH bundle —— 判据是 package.json 里的 dsh.bundle.patch(dsh-plugin-manager/lib/index.js:226-229 是唯一判定代码)。 DSH 在分发层只认 bundle:

plugin_manager action=install_bundle target="github:hu568/dsh-plugin-cluster-preset"

桌面端(Electron profile)用「设置 → 插件」或插件管理器 UI 添加同一个 spec 即可。

⚠️ 代码改动要完全重启 DSH。 dsh-hmr 的 ignored 默认含 **/node_modules, 而插件就装在 profiles/<name>/node_modules/ 下 —— 不在监听范围内; 再者 Node ESM 的 loadCache 会缓存已 import 的模块。 只有改 patch 配置才热生效。

⚠️ 更新时必须 bump version。 DSH 靠 profile 依赖差分识别装了哪个包; 内容变了而 spec 字符串没变会报 ambiguous-install。

方式一:脚本(幂等,适合本地开发)

# 安装 / 刷新
node scripts/install.mjs

# 卸载
node scripts/install.mjs --remove

它把预设块追加进 %USERPROFILE%\.dsh\profiles\desktop\cordis.patch.yml,用注释标记包起来, 重跑会替换而不是叠加。同时把 cluster-preset/ 的内容复制到 profiles\desktop\cluster-preset\,卸载时一并删除。执行前请自行备份该文件。

⚠️ 相对路径插件必须跟着补丁走。 预设的子行按声明它的补丁文件所在目录解析, 所以 YAML 里写的是 ./cluster-preset/lifecycle-reminder.js。 dsh-app-boot 的 anchorInsertedPluginNames 只会锚定 insert 顶层行与 group: true 的子数组 —— 嵌在 config.plugins 里的名字不会被改写。 手工安装时务必自己把 .js 放到那个子目录,否则行会变成一个导入失败。

⚠️⚠️ 同一个目录还必须有一个「归属清单」(package.json),且 name 与 version 都非空。 这是排查了很久才挖出来的第二个坑,症状离原因很远:

相对路径插件没有自己的清单时,向上回溯找到的最近清单是 profile 自己的 package.json, 而 DSH 生成的它只有 name、没有 version。偏偏 @deepseek-ai/dsh-plugin-package-inventory-deepseek(默认启用)会为每个请求遍历 「发起请求的那个 Agent 所在预设树」里的活跃条目并解析其归属包身份 (dsh-plugin-package-inventory-deepseek/lib/index.js 的 PackageIdentityResolver.resolve → identityFromManifest),遇到「有 name 没 version」的清单直接抛错。 适配器把这个失败包成 REQUEST_EXTENSION,于是:

TURN/END {"kind":"error","error":{"code":"REQUEST_EXTENSION",
          "message":"DeepSeek request extension preparation failed"}}

也就是说,只要用 DeepSeek 官方模型,这个预设的每一轮都会在「还没走到模型」时就死掉; 而用其它 provider(例如 profile 默认的 WorkBuddy)时那条扩展路径根本不执行, 所以单看默认模型完全看不出问题 —— 这也是它一直潜伏的原因。

修法就是随插件一起发一个 cluster-preset/package.json(install.mjs 负责复制并校验 name/version 非空)。scripts/validate.mjs 会检查它存在且字段合法, 也会检查 install.mjs 确实复制它。

方式二:手工(从 GitHub 克隆后)

  1. 把 cluster.patch.yml 的 - insert: 块内容追加进 profile 的 cordis.patch.yml
  2. 建 profiles\desktop\cluster-preset\,把 cluster-preset/lifecycle-reminder.js 复制进去
  3. 把 cluster-preset/package.json 复制为 profiles\desktop\cluster-preset\package.json (别省,见上面那条警告)

生效方式

profile 的 cordis.patch.yml 由 dsh-hmr 监听,改动会热重载 profile, 新增的预设立刻进入名册 —— 不需要重启 DSH。

装好后在新建会话时,从预设选择器里选「集群模式」即可。

预设选择作用于新会话;已有会话保持它启动时绑定的那份组成。

校验

# 结构与不变量(需要 Electron 运行时来读 asar 里的 js-yaml)
$env:ELECTRON_RUN_AS_NODE='1'
& "C:\Users\Administrator\AppData\Local\Programs\DeepSeek Harness\DeepSeek Harness.exe" scripts\validate.mjs

# 端到端:用 DSH 自己的 loader 把补丁组合到真实 profile 上
$env:ELECTRON_RUN_AS_NODE='1'
& "C:\Users\Administrator\AppData\Local\Programs\DeepSeek Harness\DeepSeek Harness.exe" scripts\preflight.mjs

# 运行时:问活着的 Host 要预设名册(能分辨「已挂载」与「激活成功」)
node scripts\probe-roster.mjs

# 运行时:逐行列出该预设的子行与相位
node scripts\probe-rows.mjs

# 行为:驱动插件的真实 agent/pre-step,断言四个阶段各触发一次
node scripts\test-lifecycle.mjs

# 运行时语义:用真实 ToolRuntime + 真实 restrict() 验证 toolFilter 名单
$env:ELECTRON_RUN_AS_NODE='1'
& "C:\Users\Administrator\AppData\Local\Programs\DeepSeek Harness\DeepSeek Harness.exe" scripts\test-restrict-scope.mjs

# 反向测试:validate.mjs 必须在「缺陷版」上失败(否则它什么也没保护)
node scripts\test-validate-negative.mjs

# 诊断:把某个会话日志(多帧 zstd)打印成紧凑事件流
$env:ELECTRON_RUN_AS_NODE='1'
& "C:\...\DeepSeek Harness.exe" scripts\session-dump.mjs <session-id 或片段>

# 真机验收(⚠️ 会花一次真实 LLM 轮次:建会话 → 委派 explore → 判成败)
$env:ELECTRON_RUN_AS_NODE='1'
& "C:\...\DeepSeek Harness.exe" scripts\acceptance-cluster-delegation.mjs cluster
# 传 standard 当对照组(便宜的健全性检查)

validate.mjs 检查:YAML 可解析、形状正确、id 唯一、每行都有包名、 必需的配置字段在位、{{变量}} 已注册、toolFilter 名字都是真实工具、 没有任何 toolFilter 命名 per-agent(不可过滤)工具、每个专家钉住 maxDepth: 1、 相对路径插件存在且命名正确、叶子约束成立。

preflight.mjs 检查:真实 js-yaml + 真实 composeEntries 能组合出 preset-cluster、 组合零警告、出厂预设未被破坏、每个包在真实 node_modules 根下都存在。

probe-roster.mjs 走 Host 的 /api RPC(agentPresets/list),报告每个预设的 broken 字段 —— 这是区分「挂上了」与「真的能激活」的唯一信号。

test-restrict-scope.mjs 是这次缺陷的回归测试:它读发布产物里的每份 deny 名单, 用真实 ToolRuntime + 真实 restrict() 跑两遍—— ① 发布形状下每份名单都必须被接受,且委托出去的子体确实看不到那些工具; ② 合成当初的 per-agent 形状(把 subagent 只注册进 Agent 自己的层), 断言它仍然复现原始报错串。②保证这条约束不会被后人遗忘。

test-validate-negative.mjs 是 validate.mjs 的反向测试:把当初的缺陷(通用行重新 带上 modelSelectionSettings: true,而 deny 名单仍在写 subagent)注入一份隔离副本, 断言 validate.mjs exit 非 0;同时断言未修改的版本仍然 VALID(否则它只是「永远失败」)。

test-lifecycle.mjs 直接驱动插件的真实 agent/pre-step 处理器(只伪造它读取的宿主服务, 不复制它的逻辑),断言:年龄换算符合文档刻度、四个阶段精确在 20/30/40/60 岁各触发一次、 跳级只提示最高档、预设/子智能体/缺服务三道闸门、注入消息满足会话的形状校验。

session-dump.mjs 是诊断工具:会话日志是多帧拼接 zstd(同步与流式 API 都只解第一帧, 所以朴素读法只会看到一个事件),它逐帧解码后打印 meta / 工具调用 / 错误 / 回合结局。

acceptance-cluster-delegation.mjs 是真机验收(会花真实模型轮次,故意不进常规套件): 用 Host 的 /api 造一个 cluster 会话 → 委派 explore → 断言「调用了 explore 且没有 restrict() 错误」。这正是当初那个 bug 的端到端判据;传 standard 则跑一个便宜的对照组。

实测结果

安装后,活着的 Host 报告:

5 preset(s):
  * standard     (no name)    ok
    ptc          (no name)    ok
    minimal      (no name)    ok
    cordis       (no name)    ok
    cluster      集群模式         ok

cluster composition inventory:
  broken: (none)
  rows: 30  phases: {"active":28,"null":2}

test-lifecycle.mjs 的行为断言:

scale: 10k/100k -> 10 years
asymmetry: 300k/1M nominal over 300k effective -> 30 years, 100% life
fired at: 20y@20000, 30y@30000, 40y@40000, 60y@60000
notice summary: 寿命论 · 20 岁 · 轻量任务可以完成(理论年龄 25.0 岁)

那 2 个非 active 行都是刻意的禁用:

行 原因
@deepseek-ai/dsh-tool-bash disabled: !!js process.platform === 'win32' —— 本机是 Windows,用 pwsh
@deepseek-ai/dsh-plugin-manager/tools 显式禁用,与出厂 standard 预设一致

7 个 dsh-tool-subagent 行全部 active,这就是专家名册本身。

重建

改动 prompts/*.md 后重新生成 YAML:

node scripts/build-preset.mjs

文件

cluster.patch.yml              生成物:可直接安装的 profile 补丁
package.json                   bundle 清单(dsh.bundle.patch)—— 分发的唯一判据
cordis.patch.yml               bundle patch 入口
cluster-preset/
  lifecycle-reminder.js        寿命论阶段提示器(零依赖,随预设分发的相对路径插件)
  package.json                 相对路径插件的归属清单(name+version 必须非空)
prompts/orchestrator.md        编排者人设
prompts/explore.md             探查者
prompts/librarian.md           书库管理员
prompts/oracle.md              神谕
prompts/metis.md               墨提斯(规划前顾问)
prompts/momus.md               摩墨斯(计划评审)
scripts/build-preset.mjs       由 prompts/ 生成 cluster.patch.yml
scripts/validate.mjs           结构与不变量校验
scripts/preflight.mjs          端到端组合校验
scripts/test-lifecycle.mjs     行为校验:驱动插件的 agent/pre-step
scripts/install.mjs            装进 / 移出 desktop profile(含复制插件文件)
scripts/probe-roster.mjs       运行时:读活着的 Host 的预设名册
scripts/probe-rows.mjs         运行时:列出该预设每个子行的相位

设计来源

编排者的工作法提炼自 oh-my-openagent 的 Sisyphus:

  • Phase 0 意图门:先说出意图再分类;动手前强制做委派检查;默认偏向委派
  • 并行扇出:独立的探查/检索一律同时发出,且绝不重复已被委派的检索
  • 委派 prompt 六段式:任务 / 期望产出 / 允许的工具 / 必须做 / 绝不要做 / 上下文
  • 会话续接:追问与修复复用同一个子智能体,不另开新的
  • 失败恢复:连续 3 次失败即停止、回退、咨询顾问、再问用户
  • 硬禁令:不压制类型错误、不擅自提交、不给没读过的代码下结论、不把代码留在坏状态

子智能体人设分别对应其 explore / librarian / oracle / metis / momus 五个专门代理。

已知边界

  • 本预设不包含 workflow 与 ralph 工具:那是另一种编排范式,与集群模式竞争同一种注意力。 需要时可在 build-preset.mjs 的收尾工具里加回。
  • 预设不是安全沙箱:YAML 与插件都能执行 Host 代码。
  • 用 deny 的 toolFilter 依赖工具名拼写正确。名字错了会在第一次委派时报错, 而不是静默放行 —— 这是刻意的。scripts/validate.mjs 就是为此存在的。
—/ 5

No ratings yet

Verified DSH bundle

Commit 421f57c090bd

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout