诉求(最重要):设置页里暴露「子代理并发度」
- 设置项:DSH 的「设置」里加一项可直接改的 子代理并发度 / subagent concurrency,并写进真正生效的那一层(不是只写进会被忽略的 host 配置层)。
- 显示有效值:设置项旁边显示当前有效值(如"有效 512(受 CPU 与 per-run binding 预算约束)"),让"我配的"与"真在用的"一眼可比。
- 联动校验:per-run binding 预算(
maxPendingCalls)与该并发度联动 —— 预算 < 并发度(加余量)时,在设置校验或 boot时报错/告警,而不是等第一次真跑时整 run 判死。 - boot 各打一行:两个有效值都打出来,便于事后核对与值班排查。
展开的四条判据、现状与本地缓解见 诉求 A;为什么今天做不到,见下面四条缺陷面。
四条缺陷面
四条叠在一起,才构成"我明明配了、实际只有 16、而且没有任何地方报错"这个静默降级;其中 ④ 还会把"慢"变成"整 run 失败"。
| # | 缺陷 | 可复跑判据(判据行) |
|---|---|---|
| ① | 部署配置层对这个插件不生效 结构性原因:这个引擎实例由 agent preset 组合实例化,而 $DSH_HOME/cordis.patch.yml 只叠在 host/profile 那一层;host 面同 id 的行还被随包 web app 显式 disabled: true。 | 配置层写 48 + 只重启该户 ⇒ 探针 max_overlap=16(FAIL,期望正好 48);同一刻 --dump-config 显示 512 / disabled: false。判据行: WF_CONCURRENCY_PROBE … n=64 … max_overlap=16 … verdict=FAIL |
| ② | 缺省值是硬编码 CPU 上限,且完全静默z.natural().default(0) + config===0 ? Math.min(16, max(1, CPU-2))。256 核 ⇒ 16;即使把常量改成 512,也只有 min(512,254)=254(CPU-2 硬顶)。 | 出厂原件两行(md5 161e274e…)+ availableParallelism=256;整份文件只有 2 处宿主侧出现该字段(无打印/校验/告警),文档也只写"由 CPU 并行度解析"。 |
| ③ | 升级静默吃掉手工适配 内核按版本目录安装 + 原子翻 symlink;新目录里是上游原件 ⇒ 任何对引擎的修补都失效,失效方式是并发掉回 16。 | 安装日志 落盘 [app-dir]/app-[version] / symlink …;新目录引擎 mtime = 安装时刻且含 16;补丁器被迫 PATCH … 16 -> 512。 |
| ④ | per-run binding 预算与并发上限不联动ptc-runtime 的 maxPendingCalls 缺省 128(每 run 一份);每个在飞 child 长挂一个 childResult binding ⇒ 它等效于"单 run 在飞 child 上限"。超限是 protocolFailure:整个 run 判死,不排队。 | 同一实例:n=127 PASS(max_overlap=127);n=128 / 129 / 300 FAIL(agents_started=0、run 只活 731 ms、错误 pending binding calls exceed configured limits)。改 1024 后 n=512 真跑到 max_overlap=512。判据行: WF_CONCURRENCY_PROBE … n=512 … max_overlap=512 … verdict=PASS |
量级(只作参考,不作 SLA):同一天同一台、同一个 n=64 探针,上限 16 时 wall_s=6.725、上限 64 时 wall_s=3.555(两次真跑,非交替 A/B)。触发本轮工作的真实批次是 674 个裁决席。
① 配置层不生效:写得进去、dump 显示成功、跑起来仍是缺省
判据不是"读配置",而是"打真进程"。探针驱动跑着的 web 进程跑一个 n 个 agent() 的 workflow,再从该会话 durable 日志的
tool-workflow/agent-start|agent-end 事件时间戳算最大重叠 —— 就是 GUI 面板上「N 个成员同时运行」那个数。下发事件发生在引擎拿到并发槽之后,
所以这个重叠数就是引擎实际放行的并发。
第 1 步:配置层写"判别值" 48,只重启该户
$ sudo -n -u user-k sudo -n python3 ops/dsh/workflow-concurrency/set-wf-concurrency.py --channel alpha --users user-e --to 48
ENGINE channel=alpha id=workflow-ptc pkg=dsh-workflow-ptc file=[app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-workflow-ptc/lib/index.js
WRITE user-e: absent -> present(blocks=1):maxConcurrentAgents=48,maxTotalAgents=4096,maxItemsPerCall=4096 (备份=cordis.patch.yml.bak-20260920-083718)
EXIT=0
$ sudo -n -u user-k sudo -n systemctl restart dsh-alpha-user-e.service
EXIT=0
$ sudo -n -u user-k sudo -n systemctl show dsh-alpha-user-e.service -p MainPID -p ExecMainStartTimestamp
MainPID=3799199
ExecMainStartTimestamp=Sun 2026-09-20 08:37:26 CST
第 2 步:探针 n=64,期望正好 48 —— ❌ FAIL
$ sudo -n -u user-k sudo -n python3 ops/dsh/workflow-concurrency/probe/measure-wf-concurrency.py --channel alpha --user user-e --n 64 --expect-exact 48
session=session-7e49011a-c7bc-4987-b310-4c0b14a5cfb7 tag=wf-concurrency-probe-1789864652-n64 n=64 login=200 cookies=1
WF_CONCURRENCY_PROBE channel=alpha user=user-e n=64 agents_started=64 max_overlap=16 run_end=completed wall_s=6.725 session=session-7e49011a-c7bc-4987-b310-4c0b14a5cfb7 verdict=FAIL
why: expect_exact=48 but max_overlap=16
EXIT=1
16 正是兜底分支 config===0 ? Math.min(16, max(1, CPU-2)) 的值 ⇒ 那个进程里 this.config.maxConcurrentAgents === 0,48 没有到达引擎。
第 3 步:换一个变量再判一次(唯一一次重试)
把代码兜底从 16 抬到 512(本机 256 核 ⇒ 兜底实际 min(512,254)=254),于是两个假设给出互不相同的数:
「配置层活 ⇒ 48」「配置层死 ⇒ 64」,不存在 16 这个第三种可能。写入 08:39:36、boot 08:39:37,排除时序偶然。
=== RETRY R1: 配置层写回 48 ===
WRITE user-e: present(blocks=1):maxConcurrentAgents=512,... -> present(blocks=1):maxConcurrentAgents=48,... (备份=cordis.patch.yml.bak-20260920-083936)
=== RETRY R2: 只重启 dsh-alpha-user-e.service ===
RESTART_EXIT=0
ALIVE after 4s
MainPID=3821304
ExecMainStartTimestamp=Sun 2026-09-20 08:39:37 CST
=== RETRY R3: 探针 n=64 --expect-exact 48 ===
WF_CONCURRENCY_PROBE channel=alpha user=user-e n=64 agents_started=64 max_overlap=64 run_end=completed wall_s=3.818 session=session-eb86db19-6d73-4feb-93a0-5e2b767eec51 verdict=FAIL
why: expect_exact=48 but max_overlap=64
同刻的 --dump-config:绿的(512 / disabled: false)
同一个 home patch、同一个 plugin id,dump 出来是这样(注释行还标着这一条正是该户 home patch 打的):
$ sudo -n -u user-k sudo -n runuser -u user-e -- env HOME=/home/user-e DSH_HOME=/home/user-e/.dsh-alpha \
[app-dir]/app-alpha/node_modules/.bin/dsh --profile infra --dump-config | grep -B3 -A10 workflow-ptc
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app, /home/user-e/.dsh-alpha/cordis.patch.yml
- id: workflow-ptc
name: '@deepseek-ai/dsh-workflow-ptc'
config:
maxConcurrentAgents: 512
maxTotalAgents: 4096
maxItemsPerCall: 4096
disabled: false
- id: tool-workflow
name: '@deepseek-ai/dsh-tool-workflow'
disabled: false
--dump-config 绿而运行期红,两者差一个组合面。2026-09-16 在稳定通道上先撞到同一个形状(当时 dump 出 64、进程里仍是 16)。结构性原因(代码/文件直读)
(a) 真正干活的条目在 agent preset 面,而且它没有任何并发配置:
$ sudo -n -u user-k sudo -n grep -n -A6 "id: workflow-ptc" \
[app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-agent-presets/presets/standard/agent.cordis.yml
222: - id: workflow-ptc
223- name: '@deepseek-ai/dsh-workflow-ptc'
224- config:
225- provider: spawn
226-
227- - id: tool-workflow
228- name: '@deepseek-ai/dsh-tool-workflow'
(b) 该 preset 自述它是一个独立的组合面,且它挂的插件是这条 standing mount 的私有实例(isolate realm):
$ sudo -n -u user-k sudo -n sed -n '1,6p' \
[app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-agent-presets/presets/standard/agent.cordis.yml
# The `standard` agent preset: the full coding agent, mounted once per process.
#
# This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
# standing scope; every session naming it joins by scope parentage, so the
# tools and prompt sections registered here cover each joined agent while a
# session's own state stays keyed per Session/Agent inside the plugins. The
(c) host 面确实有同 id 的一行,但被随包 web app 显式关掉:
$ sudo -n -u user-k sudo -n sed -n '474,490p' \
[app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-web-app/cordis.patch.yml
- id: tool-subagent-list-agents
disabled: true
- id: tool-subagent
disabled: true
- id: tool-subagent-fork
disabled: true
- id: workflow-ptc
disabled: true
- id: tool-workflow
disabled: true
(d) home patch 只叠在 profile/host 层(dsh/lib/profile-boot-*.js 的注释):"The home-level user patch layer ($DSH_HOME/cordis.patch.yml), applied over every profile's own layer."
⇒ 配 host 面那一行,改不到 session 实际用的 preset 面实例。
未主张「哪一步把 session 的 workflow 工具绑到 preset 面实例」这段代码路径我们没有逐行走完;上面四条是文件/代码直读 + 运行期实测的组合,请上游按这四处复核。
② 缺省值是硬编码的 CPU 相关上限,且没有任何提示
出厂原件(node_modules 里第一次补丁前的备份;文件 mtime = 版本目录安装时刻 ⇒ 就是 npm 装进来的那一份,没被任何人改过):
$ F=[app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-workflow-ptc/lib/index.js.bak-20260920-083803
$ sudo -n -u user-k sudo -n bash -c 'ls -la --time-style=full-iso '$F'; md5sum '$F'; grep -n "maxConcurrentAgents: z.natural\|maxConcurrentAgents === 0 ? Math.min" '$F''
-rw-r--r-- 1 root root 65629 2026-09-18 16:37:20.556490038 +0800 …/dsh-workflow-ptc/lib/index.js.bak-20260920-083803
161e274e528c7340b324f1c71e77984e …/dsh-workflow-ptc/lib/index.js.bak-20260920-083803
587: maxConcurrentAgents: z.natural().default(0),
619: maxConcurrentAgents: this.config.maxConcurrentAgents === 0 ? Math.min(16, Math.max(1, availableParallelism() - 2)) : this.config.maxConcurrentAgents,
| 事实 | 原始依据 |
|---|---|
缺省 0 不是"不限制",而是"走 CPU 推导",且推导式把上限钉死在 16 | index.js:587 / :619(上) |
本机 availableParallelism=256 cpus=256 ⇒ 用户拿到 16 | $ sudo -n -u user-k sudo -n node -e 'const os=require("os");console.log("availableParallelism="+os.availableParallelism()+" cpus="+os.cpus().length)' → availableParallelism=256 cpus=256 |
就算把常量 16 改成 512,仍有 Math.min(N, CPU-2) 硬顶 ⇒ 本机最多 254;想正好 256 只能靠"配置层被读到",而那正是 ① 里证明读不到的一层 | :619 的 Math.min 结构 |
| 整份文件没有一处打印/校验/告警这个值 | grep -c "maxConcurrentAgents" …bak-20260920-083803 → 3(2 处在宿主侧 schema/解析式,1 处在内嵌 guest 源码字符串的运行期槽位比较里) |
| 随包文档只说"由可用 CPU 并行度解析",不给公式、不提 16 硬顶、不说该写在哪一层 | dsh-workflow-ptc/README.md:42:| maxConcurrentAgents | 0 | Concurrent agent() ceiling; 0 resolves from available CPU parallelism. | |
用户侧的可观测性为零:GUI 里没有这个旋钮,面板显示的"同时在飞的成员数"是结果不是配置;日志里没有一行说明解析出的上限。被压到 16 的唯一症状是"跑得慢"。
③ 升级会静默吃掉手工适配(换版本目录 ⇒ 补丁失效 ⇒ 并发掉回 16)
部署形态是"每个版本一个目录 + 原子翻 symlink"。以下是升级器自己的日志(root 只读):
[2026-09-18 16:36:52 +08:00] 装内核:npm install --prefix=[app-dir]/.alpha-staging-[version] --no-audit --no-fund --registry=https://registry.npmmirror.com/ @deepseek-ai/dsh@0.1.6-alpha.2
[2026-09-18 16:37:31 +08:00] 落盘 [app-dir]/app-[version]
[2026-09-18 16:37:31 +08:00] symlink [app-dir]/app-alpha -> [app-dir]/app-[version]
新目录里的引擎就是上游原件(② 里那份备份的 mtime 2026-09-18 16:37:20 = 同一次安装,内容含 16),所以对引擎的任何修补都必须重打:
$ sudo -n -u user-k sudo -n python3 ops/dsh/workflow-concurrency/patch-wf-fallback.py --to 512
PATCH [app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-workflow-ptc/lib/index.js 16 -> 512 备份=index.js.bak-20260920-083803
PATCH_WF_FALLBACK files=3 changed=1 rc=0
不重打会怎样:并发从 254/512 掉回 16,而且是没有报错的退化 —— 没有日志、没有告警、没有失败,只有墙钟变长;升级脚本自己的实例体检(服务活性 + 入口码)看不出来。
本地已做缓解(不是上游已修):本仓把"补兜底"钉进了升级器 ops/dsh/alpha-update.py 的 install_version()(翻 symlink 之前执行),失败 loud 不阻断。升级器 dry-run 的原始输出:
[2026-09-20 08:42:52 +08:00] [DRY] 补并发兜底(翻 symlink 之前):python3 [app-dir]/bin/patch-wf-fallback.py --root [app-dir]/app-[version] --to 512。
未做 任务书给的判据是"对比升级前后同一探针的 max_overlap"。我们没有真的触发一次升级(那要动 alpha 通道全部 24 户),所以本条只给到文件级 + 脚本级证据,升级前后各跑一次探针的对照没有做。
④ per-run binding 预算(maxPendingCalls 缺省 128)与并发上限不联动:超限是整 run 判死
- 位置:
@deepseek-ai/dsh-ptc-runtime-node/lib/index.js:777maxPendingCalls: z.number().default(128);:1109一旦++pending > maxPendingCalls就protocolFailure("pending binding calls exceed configured limits")。计数器pending声明在每次 spawn worker 的处理闭包里(同文件 ~:1020)⇒ 每个 run 一份预算。 - 为什么它等于「在飞 child 上限」:workflow 引擎每个在飞 child 都长挂一个
childResultbinding(dsh-workflow-ptc的startAgent→host.childResult({callId}),child 结束才释放)⇒ 一个 n 路扇出同时占 n 个 binding。
实测边界(alpha-user-e,0.1.6-alpha.2,同一实例)
n=127 WF_CONCURRENCY_PROBE … agents_started=127 max_overlap=127 run_end=completed verdict=PASS
n=128 WF_CONCURRENCY_PROBE … agents_started=0 max_overlap=0 run_end=error verdict=FAIL
durable 日志错误原文: pending binding calls exceed configured limits
run-start → run-end = 731 ms(一个 agent 都没起)
n=300 (同形,同样判死)
旁证(预算就是那道墙):把该预算从缺省 128 提到 1024 后,同一实例 n=512 真跑到 512:
$ … set-wf-concurrency.py --channel alpha --users user-e --to 512 --pending 1024
CHECK user-e: present(blocks=1):maxConcurrentAgents=512,… ptc=present(blocks=1):maxPendingCalls=1024
$ … systemctl restart dsh-alpha-user-e.service
$ … measure-wf-concurrency.py --channel alpha --user user-e --n 512 --expect-at-least 512
WF_CONCURRENCY_PROBE channel=alpha user=user-e n=512 agents_started=512 max_overlap=512 run_end=completed wall_s=32.061 verdict=PASS
它住在 host 组合的 ptc-runtime 插件里,所以 $DSH_HOME/cordis.patch.yml 对它确实生效(与 ① 的 preset 面正好相反)—— 判据是"改完重启后真跑一次",不是"dump 里有"。
诉求 A(用户要求):设置页里要有「子代理并发度」
并发度是用户可见的资源旋钮,现在却只能靠改 node_modules 里的常量、或写一份会被静默忽略的 YAML 才能改。具体要求:
- 设置项:DSH 的「设置」里增加一项可直接改的 子代理并发度 / subagent concurrency,并且必须写进真正生效的那一层(不是只写进会被忽略的 host 配置层)。
- 显示有效值:设置项旁边显示当前有效值(例如"有效 512(受 CPU 与 per-run binding 预算约束)"),让"我配的"和"真在用的"一眼可比。
- 联动校验:per-run binding 预算(
maxPendingCalls)与该并发度联动 —— 预算 < 并发度(加余量)时,在设置校验或 boot时报错/告警,而不是等第一次真跑时整 run 判死。 - boot 日志各一行:两个有效值都打出来,便于事后核对与值班排查。
本地已做的缓解(不是上游已修):本仓自己写了配置层写入器 + 内核双轴补丁 + 每日升级后自动重打 + 总闸腿,才把 24 户 alpha 稳定在 512。这些能力本该由产品提供,而不是每个部署各自维护一套补丁。
影响
- 256 核机器上,一次 fan-out 被压到 16 路;
min(CPU-2)又让"改代码常量"最多到 254,配置层没生效 ⇒ 大机器上永远拿不到用户显式要的值。 - 触发本轮工作的真实批次:674 个裁决席(2026-09-15 用户裁定「16 太慢」)。
- 同机同日同探针(n=64):上限 16 ⇒
wall_s=6.725;上限 64 ⇒wall_s=3.555(两次真跑,约 1.9×,只作量级参考)。 - 失败模式是静默降级:dump 绿运行红 + 缺省值不可见 + 升级抹掉补丁 —— "我明明配了"与"实际只有 16"之间没有一处会报错。
- ④ 把失败模式从"慢"变成"整 run 失败":并发上限 512 与 binding 预算缺省 128 矛盾时,任何 >127 在飞的扇出都以
pending binding calls exceed configured limits整体判死(n=127 过 / n=128 死)。 - 准入 ≠ 吞吐(同一实例 trivial agent 实测):300 席在
engine=8时wall_s=55.47(≈5.4 agent/s),engine=512时wall_s=13.77(≈21.8/s);512 席时wall_s=32.06(≈16.0/s)⇒ 准入买得到墙钟(约 4×)但边际递减,等效"真在推进"约 20–25。单线程 in-process driver 造成的吞吐天花板另有 issue 在跟(ops/dsh/upstream-issue-long-fanout-reliability/面二)。
诉求(fix 建议,按优先级)
- preset 组合面必须能继承或被 host/部署配置层覆盖(同一 plugin id 的有效值应只有一个),或者在该插件行的 config 被忽略时 loud 警告一次(例如 boot 打一行
workflow-ptc: host-plane config ignored; effective maxConcurrentAgents=<v> (preset plane))。至少要文档写明:这个旋钮该写在 agent preset 的agent.cordis.yml里,而不是$DSH_HOME/cordis.patch.yml。 - 缺省值不该是 CPU-2 的硬上限:
0应解析为"仅受资源与maxTotalAgents约束",或至少把Math.min(16, …)这个 16 去掉/可配;有效值要能在 GUI 与日志里看到。 - 文档写明这个旋钮在哪一层生效:
dsh-workflow-ptc/README.md现在只说"0resolves from available CPU parallelism",既没给公式,也没说 host 面被 web appdisabled: true、真正生效的是 preset 面。 - (用户要求,见上节「诉求 A」)在设置页暴露「子代理并发度」:设置项写进真正生效的那一层 + 显示有效值 + 与 per-run binding 预算联动校验(预算 < 并发度时在设置/启动时报错,而不是真跑时判死)+ boot 各打一行。
证据强度与未主张
| 等级 | 覆盖 |
|---|---|
| 已实测 | 配置层 48 → max_overlap=16;兜底 512 后 48 → 64;schema 默认值 48 → 正好 48(判据一,证明生效轴);n=127 过 / n=128 死(binding 预算);预算改 1024 后 n=512 → max_overlap=512;24 户逐户 n=64 → 64;同刻 --dump-config 512/disabled:false;出厂原件两行与 md5;availableParallelism=256;安装日志与补丁器输出。全部带原始命令与原始输出。 |
| 代码/文件直读 | preset 面条目、preset 头部自述、web app disabled:true、profile-boot 的 home patch 注释、README:42。 |
| 未主张 / 未做 | ① "session 的 workflow 工具绑定到 preset 面实例"的代码路径未逐行走完;③ 升级前后各跑一次探针的对照未做(等价证据:新目录含上游原件 + 升级器已改成翻 symlink 前自动重打 + 其 --selftest 24/24)。512 已实测(n=512 → max_overlap=512),不再是推断。 |
| 采集期噪声 | 2026-09-20 08:48–08:49 另一并发会话正对同一份 alpha 引擎文件来回改 16/48/512(--show 08:48 报 fallback=512 default=512 engine=512,08:49:31 报 fallback=48 default=48 engine=48;文件 mtime 08:49:18)。本文"出厂值"一律以备份 .bak-20260920-083803 为准,不取那一刻的现场值。 |
采集环境:@deepseek-ai/dsh@0.1.6-alpha.2([app-dir]/app-alpha),引擎 @deepseek-ai/dsh-workflow-ptc@0.1.6-alpha.2,
Linux 6.8.0 x86_64 / Ubuntu 22.04.5 / Node v24.16.0,256 核。稳定通道(@deepseek-ai/dsh-workflow-worker-thread,[app-dir]/app-[version])上先撞到同一形状。
中文材料与完整原始记录:ops/dsh/upstream-issue-workflow-concurrency-not-a-config-knob/README.md;
运行期记录:ops/dsh/workflow-concurrency/evidence/alpha-512/canary-user-e.md。
可直接粘贴的英文正文(issue body)
Title
Expose subagent concurrency in DSH Settings: the workflow concurrency ceiling (maxConcurrentAgents) cannot be configured today, and its default silently caps fan-out at min(16, CPUs-2)
Summary
`maxConcurrentAgents` — the ceiling on concurrent `agent()` calls in a workflow run — cannot be set
from the deployment configuration layer. Writing it into the home patch layer
(`$DSH_HOME/cordis.patch.yml`) succeeds and `dsh --profile <p> --dump-config` shows the merged value,
but the engine instance that actually serves a session never receives it: it keeps the schema default
`0` and falls back to a hard-coded `Math.min(16, Math.max(1, availableParallelism() - 2))`. On a
256-CPU host that is 16. Nothing logs, warns or displays the effective value, and because installs
land in per-version directories, any local patch to the engine is silently lost on the next upgrade.
Primary ask (feature request)
Expose **subagent concurrency** as a user-visible setting in DSH Settings. Requirements:
1. The setting must write to the layer that is actually honored (today the deployment config layer is
silently ignored for this plugin), and the effective value must be displayed next to it.
2. Couple the per-run binding budget (`ptc-runtime.maxPendingCalls`, default 128) to that ceiling:
validate `maxPendingCalls >= maxConcurrentAgents` (plus headroom) at settings/boot time instead of
failing an entire workflow run later with a fatal protocol error.
3. Log both effective values once at boot so "configured" and "effective" can be compared without
reading node_modules.
Environment
- `@deepseek-ai/dsh@0.1.6-alpha.2` (install root `[app-dir]/app-alpha` -> `[app-dir]/app-[version]`)
- Engine: `@deepseek-ai/dsh-workflow-ptc@0.1.6-alpha.2`
(`node_modules/@deepseek-ai/dsh-workflow-ptc/lib/index.js`)
- Same shape observed on the stable channel with `@deepseek-ai/dsh-workflow-worker-thread` (0.1.5)
- Linux 6.8.0 x86_64, Ubuntu 22.04.5, Node v24.16.0, `availableParallelism() = 256`
- Sessions run under the shipped agent preset
`@deepseek-ai/dsh-agent-presets/presets/standard/agent.cordis.yml`
Steps to reproduce
1. Set the value in the deployment config layer and restart the instance:
python3 set-wf-concurrency.py --channel alpha --users <u> --to 48 # writes
# $DSH_HOME/cordis.patch.yml -> - id: workflow-ptc
# config: {maxConcurrentAgents: 48, ...}
# disabled: false
systemctl restart dsh-alpha-<u>.service
Confirm the merge:
runuser -u <u> -- env HOME=/home/<u> DSH_HOME=/home/<u>/.dsh-alpha \
[app-dir]/app-alpha/node_modules/.bin/dsh --profile infra --dump-config | grep -A6 workflow-ptc
# - id: workflow-ptc
# config:
# maxConcurrentAgents: 48
# disabled: false
2. Measure the real overlap on the *running* process (the probe drives the live web process with a
workflow of N `agent()` calls and computes the maximum overlap from the durable session log's
`tool-workflow/agent-start|agent-end` timestamps — the same number the GUI panel shows):
python3 measure-wf-concurrency.py --channel alpha --user <u> --n 64 --expect-exact 48
Observed:
WF_CONCURRENCY_PROBE channel=alpha user=<u> n=64 agents_started=64 \
max_overlap=16 run_end=completed wall_s=6.725 verdict=FAIL
why: expect_exact=48 but max_overlap=16
`16` is exactly the fallback branch (`config === 0`).
3. Change one variable (patch the fallback constant to 512, so the two hypotheses predict different
numbers: 48 if the config layer is live, 64 = CPU-derived fallback if it is not), restart, re-run:
# ... patch fallback 16 -> 512 ...
python3 measure-wf-concurrency.py --channel alpha --user <u> --n 64 --expect-exact 48
# max_overlap=64 ... verdict=FAIL why: expect_exact=48 but max_overlap=64
48 never appears. Same run with the config layer left at 512 and the patched fallback gives
`max_overlap=64` as well — i.e. the effective ceiling is decided by the code fallback only.
Structural cause (why the config layer cannot reach it)
- `$DSH_HOME/cordis.patch.yml` is applied over the profile/host layer only
("The home-level user patch layer (`$DSH_HOME/cordis.patch.yml`), applied over every profile's own
layer" — `dsh/lib/profile-boot-*.js`).
- The shipped web app disables the host-plane rows outright:
`dsh-web-app/cordis.patch.yml`: `- id: workflow-ptc / disabled: true` and
`- id: tool-workflow / disabled: true`.
- The engine a session actually uses is mounted by the agent preset, whose row carries no
concurrency config at all:
# dsh-agent-presets/presets/standard/agent.cordis.yml:222
- id: workflow-ptc
name: '@deepseek-ai/dsh-workflow-ptc'
config:
provider: spawn
The preset header says it is an AGENT-PLANE composition, mounted once, and that each service row
must live in its own `isolate` realm ("this standing mount's own private instance, apart from every
other preset's").
- Consequence: the home patch's same-id row is merged into the host tree (hence green
`--dump-config`) and never reaches the preset-plane instance the session resolves.
Expected vs Actual
- Expected: setting `maxConcurrentAgents` in the deployment config layer sets the session's workflow
fan-out ceiling; the effective value is visible somewhere (log/GUI); an ignored config entry is
reported.
- Actual: `--dump-config` reports the configured value with `disabled: false`, while the running
engine uses the schema default `0` -> `Math.min(16, availableParallelism() - 2)` = 16 on a 256-CPU
host. No log line, no warning, no user-visible surface.
Second defect: the default is a hard CPU-derived cap, silently applied
Pristine shipped file (md5 161e274e528c7340b324f1c71e77984e):
dsh-workflow-ptc/lib/index.js:587:
maxConcurrentAgents: z.natural().default(0),
dsh-workflow-ptc/lib/index.js:619:
maxConcurrentAgents: this.config.maxConcurrentAgents === 0
? Math.min(16, Math.max(1, availableParallelism() - 2))
: this.config.maxConcurrentAgents,
- `0` does not mean "unlimited"; it means "derive from CPUs", capped at 16.
- Even raising the constant to 512 leaves `Math.min(N, CPUs - 2)`: on this 256-CPU host that is 254,
so a large machine can never reach its request through the fallback — and the layer that could
(config) is the one proven ineffective above.
- The whole file mentions `maxConcurrentAgents` only in those two host-side places (a third occurrence
is inside the embedded guest source string); nothing prints, validates or warns.
- Documentation says only: `| maxConcurrentAgents | 0 | Concurrent agent() ceiling; 0 resolves from
available CPU parallelism. |` (`dsh-workflow-ptc/README.md:42`) — no formula, no 16 cap, and no
statement of which composition layer the setting must be written to.
Third defect: upgrades silently discard the manual adaptation
Installs are per-version directories with an atomic symlink flip:
[2026-09-18 16:36:52] npm install --prefix=[app-dir]/.alpha-staging-[version] ... @deepseek-ai/dsh@0.1.6-alpha.2
[2026-09-18 16:37:31] 落盘 [app-dir]/app-[version]
[2026-09-18 16:37:31] symlink [app-dir]/app-alpha -> [app-dir]/app-[version]
The freshly installed engine is the pristine upstream file (mtime = install time, contains the 16
fallback), so any patch must be re-applied on every upgrade:
PATCH [app-dir]/app-[version]/node_modules/@deepseek-ai/dsh-workflow-ptc/lib/index.js 16 -> 512
If it is not re-applied, the ceiling silently drops back to 16: no error, no log, no alert, and the
instance health check still passes (it verifies liveness and entry-point status codes, not the
concurrency value). Locally we worked around this by pinning the patch into our update script before
the symlink flip; that is a workaround, not a fix.
Fourth defect: a per-run binding budget that contradicts the concurrency ceiling
`@deepseek-ai/dsh-ptc-runtime-node` caps pending binding calls **per run** at `maxPendingCalls = 128`
(schema default; `lib/index.js:777`, enforced at `:1109` with
`protocolFailure("pending binding calls exceed configured limits")`). Every in-flight workflow child
holds one long-lived `childResult` binding, so the budget is effectively the per-run in-flight child
cap. Measured on one instance: n=127 passes (`max_overlap=127`), n=128/129/300 fail with
`agents_started=0` and a 731 ms run lifetime — the run dies, it does not queue. Raising the budget to
1024 (host-plane `ptc-runtime` plugin, so the deployment config layer does apply there) made n=512
reach `max_overlap=512`.
Feature request: expose subagent concurrency in Settings
1. Add a user-visible **subagent concurrency** setting. It must write to the layer that is actually
honored, and the effective value must be shown next to it.
2. Couple the per-run binding budget to that ceiling: reject or warn at settings/boot time when
`maxPendingCalls < maxConcurrentAgents` (plus headroom), instead of failing the whole run later
with a fatal protocol error.
3. Log both numbers once at boot so "configured" and "effective" can be compared without reading
`node_modules`.
Impact
- On a 256-CPU host, fan-out is capped at 16 concurrent `agent()` calls and the operator has no way to
see or change it through the documented configuration layer.
- The degradation is undetectable by exit codes or health checks: the only symptom is longer wall
clock. In our measured pair the same n=64 probe took 6.725 s at ceiling 16 and 3.555 s at ceiling 64
(two runs, same host/day; order-of-magnitude evidence only).
- A real batch that motivated this: 674 adjudication agents, which is why 16 was reported as "too slow".
Suggested fix (priority order)
1. Make the preset-plane row inherit or be overridable by the host/deployment config layer for the
same plugin id — or, at minimum, emit one loud warning at boot when a configured row for this
plugin is ignored, including the effective value. Document explicitly which composition layer owns
`maxConcurrentAgents`.
2. Do not default to a hard CPU-derived cap. `0` should mean "bounded only by resources and
`maxTotalAgents`" (or make the cap configurable), and the effective value should be visible in the
GUI and in one boot log line.
3. Document the resolution rule (formula + cap + layer) in `dsh-workflow-ptc/README.md`.
4. Expose **subagent concurrency** in Settings: write to the honored layer, show the effective value,
and validate `maxPendingCalls >= maxConcurrentAgents` (plus headroom) at settings/boot time.
Related (not claimed in this issue): the in-process, single-threaded subagent driver caps real
throughput well below the ceiling (300 trivial agents: 55.5 s at ceiling 8 vs 13.8 s at ceiling 512;
512 agents: 32.1 s — i.e. ~20-25 effective parallel). Tracked separately.
Reproduction assets (self-contained probe, no network, no third-party dependency)
- `set-wf-concurrency.py` (writes the config layer), `patch-wf-fallback.py --show/--to N` (shows/patches
the fallback constant across install roots)
- `measure-wf-concurrency.py --channel <stable|alpha> --user <u> --n N --expect-exact E`
(drives the live process and prints `WF_CONCURRENCY_PROBE ... max_overlap=<n> verdict=<PASS|FAIL>`)