dsh-agent-teams-limits
Verifieddsh-agent-teams-limits · v0.1.0 · MIT · Web UI
DSH plugin: Settings → Plugins card for AgentTeams member cap and per-team concurrency cap.
Install
dsh plugin add dsh-agent-teams-limits Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Readme
dsh-agent-teams-limits
从社区安装 ·
dsh plugin --profile <profile> add dsh-agent-teams-limits或从源码:dsh plugin --profile <profile> add github:mimajiushi/dsh-plugins#path:/plugins/dsh-agent-teams-limits下文里的node scripts/install.mjs是源码检出时的本地安装:把包link:进你自己的 profile。
给 DSH 加一个设置卡片:AgentTeams 的成员上限(一个团队最多几个人)和并发上限(同一团队最多几个成员同时干活)。
- 位置:Web GUI →「设置 → 插件 → 插件配置」→ 展开「Agent Teams 限制」。
- 两项都留空 = 保持原样:成员上限用 AgentTeams 自己的默认(8),并发不限。装完不改设置等于没装。
- 改设置立即生效,不需要重启 DSH、不需要刷新页面。
为什么需要这个插件
@nanmicoder/[email protected] 里 maxMembers 只是一个组合配置的默认值:
// lib/index.js:84 配置模式
maxMembers: z.natural().min(1).default(8),
// lib/index.js:154 取值(apply 时快照;补丁前是 :111)
maxMembers: config.maxMembers ?? 8,
它管两处:lib/profiles.js:194(内联/配置 profile 的花名册人数,超了就是
profile "inline-plan" has 10 members but maxMembers is 8)和 lib/tools.js:971
(agent_teams_add_member 的人头闸;补丁前是 :965)。这个插件没有注册任何 settings 命名空间,
所以设置页面里本来没有这一项——只能手改 profile 的 cordis.patch.yml。
并发上限则是上游根本没有的功能:lib/scheduler.js 会把每个就绪任务直接派给每个空闲成员,
没有任何节流点、也没有对应配置字段。
怎么做到的(都不改第三方源码的配置语义)
宿主半(lib/index.js + lib/logic.js):
| 缝 | 作用 | 出处 |
|---|---|---|
全局 internal/config 瀑布({ global: true }) |
每次 AgentTeams 行被加载(含重启)时,把两个值注入它的 config。只改目标行,其它键原样保留;没配值时返回 next(),一个字节都不动 |
与 dsh-compact-model 同一条缝,本机宿主日志有实证 |
活体桥 __limits(本地补丁提供) |
改设置时直接改写已挂载实例的 resolved config。两个值都是取用时刻读取——maxMembers 由 tools 直接读 resolved,maxConcurrentMembers 由 scheduler 通过补丁加进 installTeamScheduler 调用点的活体 getter 读回 resolved——所以立刻生效;桥找不到时只告警一次并说明「下一次启动才生效」 |
主路径走 fiber 的 runtime.callback.__limits,兜底走 globalThis[Symbol.for('dsh.agent-teams.limits')] |
ctx.settings.register('agent-teams-limits', …) |
持久化到 ~/.dsh/settings.yaml,scope.watch 实时同步 |
dsh 0.1.5 的用户设置服务 |
ctx.systemPrompt.section({ order: 119 }) |
配了值才追加一行「本机限制」,让队长不去撞墙;没配值返回空串(该段被丢弃,提示与未装插件逐字相同) | 118 是审批覆盖、117 是队长协议 |
客户端 settings.plugin.item 座位 + ctx.settingsScope |
卡片本体(数字输入),0.1.7 线改挂插件管理器的 plugins.bundle.config 座位 |
与 dsh-llm-retry-all / dsh-compact-model 同一套 |
并发上限需要配套补丁(成员上限不需要):
node scripts/patch-agent-teams-limits.mjs --apply
node scripts/verify-agent-teams-limits.mjs
补丁做五件事(可用 --revert 整段还原,备份后缀 .bak-agent-teams-limits;从第一代补丁升级要先 --revert):
lib/index.js:配置字段maxConcurrentMembers(默认0= 不限)、resolved透传、__limits活体桥;lib/tools.js:把installTeamScheduler的实参从拷贝字面量改成对resolved的活体 getter——这是并发闸能不能生效的关键(第一代补丁漏了这一步,scheduler 拿到的 config 里根本没有这个字段,闸恒为「不限」;2026-09-27 独立复审发现,verify-agent-teams-limits.mjs的「负控制」用例就是钉这条);lib/scheduler.js:memberCap/occupantHoldsSlot/holdsClaim/idleEdgePark/memberOccupiesSlot/activeMemberCount+ fresh 认领前的上限判定(恢复已有 attempt 永不拦;两类 park 分开看,见下)+ 空闲边界改用kickTeam补位 + 邮箱投递被闸不再吞掉整个回合;lib/tools.js:dispatchMember里对steer唤醒同样受上限约束——到上限时返回false,这是上游自己的「现在不行」语义:消息留在邮箱,空位出现后重投,不丢消息(可能等一整个成员回合)。被闸的那一轮不再整轮白等:scheduler 会把 release 后的流程落到认领分支(那里的 cap 判定同样拒新任务,但不会拦已有 attempt 的 recovery);lib/types/index.d.ts:声明同步。
安装
cd plugins/dsh-agent-teams-limits
node scripts/install.mjs --profile desktop --dry-run # 先看规格
node scripts/install.mjs --profile desktop # 实装
脚本把宿主半唯一运行时依赖 @deepseek-ai/schemastery junction 进本目录 node_modules →
备份 profile 的 package.json 为 package.json.bak-agent-teams-limits(仅首次)→
跑 dsh plugin --profile desktop add link:<本目录> → 复核 dsh.profile.bundles 与 --dump-config 里真有这一行。
装完重启 DSH Desktop(宿主半在启动时装配;补丁也是那时被 import 的)→ 硬刷新页面(Ctrl+Shift+R)。
卸载:node scripts/install.mjs --profile desktop --uninstall(settings.yaml 里的值保留但不再生效)。
验证
node --test # 33 项:纯逻辑 / 宿主装配 / 客户端卡片
node scripts/verify-agent-teams-limits.mjs --prove-gate # 补丁闸门(含未修补基线的自证)
真机验收(重启后):
- 展开卡片,成员上限填
16、并发上限填2→~/.dsh/settings.yaml出现agent-teams-limits: { maxMembers: 16, maxConcurrentMembers: 2 }; - 宿主日志
%APPDATA%\DSH Desktop\logs\host\dsh-<date>.log出现recognised the AgentTeams row via entry-name/injected … at load time/live-applied … to N mounted AgentTeams instance(s); - 新会话让队长建一个 12 人的内联计划(
tasks: [],不会真起 12 个子 Agent)→ 被接受;把上限调回 6 再试 → 被拒,错误里的数字变成 6; - 并发上限=1 + 3 成员 3 任务 → 一次 kick 只派 1 个(
agent_teams_status里同一时刻只有 1 个working,其余 pending;前一个空闲后自动补位)。注意真机上子回合是排队启动的,所以「同一时刻」以任务/回合为准,日志里可能看到极短的重叠窗口。
边界
补丁会被插件升级覆盖:升级
@nanmicoder/dsh-agent-teams后重跑--apply+ 复核;没打补丁时,插件会在重试窗口用尽后告警一次「并发上限 inert」,成员上限仍会在下一次启动生效,不静默。启动早期(agent-teams 那一行还没挂载)重试路径不会误报;此时若你正好编辑设置,scope.watch那条路径仍会立刻报一次「桥不存在」——那是真实状态(那一刻确实没有桥可用),不是 bug。官方端(0.1.7 + agent-teams 0.1.21)本次不动:patcher 只认 0.1.20 的锚点,版本不符直接拒绝,不猜改。
上限调小不撤销已经在跑的成员,下一次派发才按新值。
「占用一个并发位」的定义:一个成员占位,当且仅当满足任一(
stopping/removed一律不计):- live AgentHandle 存在且不是
idle(真的在跑); - 握着 open claim(该成员名下有
claimed/in_progress的任务)且这个 claim 不是「idle 边界 park」——刚认领的成员投递只是排队,句柄那时还是idle;少了这条信号整个 fan-out 会被一次放过(第二轮复审抓到),少了 park 的区分又会每恢复一个就多放一个(第三轮复审抓到)。attemptId === undefined的 claim 保守计入。
两类 park 必须分开看,靠 durable 状态区分(idle 边界会同一步把成员置成
idle):- idle 边界 park(
syncMemberStatus:回合结束、attempt 仍开)→ 不占位,否则小上限会被卡死; - recovery park(
kickMember给换代 attempt 记账、成员仍是working)→ 占位,那一轮它确实要被投递。
另外:句柄明确
idle且没有 open claim 的working(漏了 idle 事件、重启残留)不占位;而「状态是idle但握着 claim 且没有 park 记录」(重启残留的任务)占位——上游下一次 kick 会 recovery 它,它真的会跑。对已在跑成员的 steer 不重复计数;正在停止(stopping)的成员不占位,所以上限可能被瞬时超过 1(它马上要退出)。- live AgentHandle 存在且不是
到上限时给空闲成员的消息会延后:
steer唤醒同样吃并发位,被闸时消息 release 回邮箱、下一次 kick 重投(不会丢,但可能等一整个成员回合);满位时该成员那一轮不会认领新任务,但会继续走认领分支(fall-through),所以它自己已开的 attempt 仍能被 recovery;一旦有空位,它就可能先认领任务、邮件稍后再投。两条闸的细微差别(有意):任务认领闸用
memberOccupiesSlot(..., parkedAttempts)(带 park 账本);steer闸在tools.js里没有那份账本,因此把「已 park」也算占位——方向是保守(只会让某条消息多等一会儿,不会超发,也不会丢消息)。提示段只在配了值时出现,改值会让该轮 prompt 前缀缓存失效一次(与同族插件相同代价)。
非 loopback 页面设置只读,卡片自动置灰——DSH 设置子系统的既有约束。