ErrorLst/dsh-code-pipeline ↗★ 0

@dsh-external/dsh-code-pipeline

为代码流水线预设动态注入阶段子代理及模型配置的插件 适合使用PTC流水线、需要动态调整各阶段模型和并发限制的用户。

包名
@dsh-external/dsh-code-pipeline
兼容性
待验证
版本
0.4.7
许可证
MIT
最近更新
2026年9月20日

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:ErrorLst/dsh-code-pipeline

@dsh-external/dsh-code-pipeline

DSH bundle plugin:为 code-pipeline agent 预设(PTC Code Mode 流水线)动态注入 阶段子代理工具,并允许在设置页配置各阶段子代理使用的模型。

解决的问题

code-pipeline 预设原本把 3 个 dsh-tool-subagent 行(subagent_plan / subagent_impl / subagent_review)以及每个阶段的 provider/model/persona/toolFilter 静态钉死在 agent.cordis.yml 里。改模型 = 改 YAML = 重启会话。

本插件把「注册哪些工具」与「用哪个模型」解耦:

  • 工具注册(静态):监听 agent/created,对组合了 code-pipeline 预设的 ROOT 代理,在其自身作用域(agent.ctx)注册 3 个阶段工具。子代理(阶段代理) 不注入——它们的 persona / 只读工具面由父代理通过 subagents.start 请求传入。
  • 模型选择(动态):每个阶段工具每次调用时读取设置命名空间 code-pipeline($DSH_HOME/settings.yaml 的 code-pipeline 节),即时生效。
  • 设置页(浏览器):Settings → 代码流水线,每阶段配置 enabled / provider / model / reasoningEffort / maxConcurrency,provider/模型列表来自 GET /dsh-code-pipeline/options(不可用时相应字段禁用并提示,不允许手输); 每阶段卡片还显示「当前运行 N / 上限 M / 已创建 K」与已创建子代理清单,数据来自 GET /dsh-code-pipeline/status(每 5 秒轮询)。

角色边界(硬约束)

  • subagent_plan 只做规划:不审查、不审计、不审批代码;
  • subagent_review 只做审查:不做规划、不做设计;
  • 两个阶段的 persona 都明确写出该边界,并在收到另一类任务时要求子代理声明自己的 角色并拒绝执行;工具的 description 也标注了「PLANNING ONLY / REVIEWING ONLY」, 防止主代理把审查任务误派给 plan、把规划任务误派给 review。
  • 阶段不可用 = 结束任务:阶段工具调用报错(阶段被禁用/未配置、provider/凭据 缺失、provider 未注册、子代理启动失败)时,工具错误信息携带明确的 「STOP and report to the user」指令,预设 persona 的 invariants 也硬性规定 主代理不得自己接手任务(不代做实现/规划/审查、不换路由、不找替身), 而是告知用户原因并等待决定。 例外(不算阶段不可用,不得终止任务):插件自己的运行并发闸门与宿主的 「同时存活子代理」容量上限——运行并发超限、宿主 ACTIVATION_LIMIT_REACHED / subagent/delivery-unavailable(dsh 0.1.6-alpha.2 起)。这些是瞬时拒绝,阶段本身 健康;错误文案会明确写「NOT stage unavailability」并给出「等名额释放后在后续步骤重试/派发 新子代理」的出路(见「每阶段并发上限与并行派发」)。

中途改需求:pipeline_followup(插入,不排队)

阶段子代理已经派发并开始干活后,用户改了需求 → 主代理用 send_message 只能 靠模型自己找到子代理 id;pipeline_followup 是流水线自己的"插话"工具:

  • 参数 child:latest(本代理最近派发的阶段子代理)| 阶段键 plan / impl / review(含中文别名 规划/计划/实现/评审/审查)| 完整 subagentId(session-...,也支持唯一前缀);
  • 参数 message:要插入的需求变更文本(完整、自包含——子代理没有本对话上下文);
  • 参数 files(必填):本轮要读的路径,一行一个,与三个阶段工具的 files 共用同一套契约 与渲染。真清单会渲染成「先用一个 run_code 程序把每个路径读完」的硬指令;单个 - 显式声明「本轮没有候选清单」(子代理自己做侦察)。省略 ⇒ 拒绝并点名 files。为什么必填: 复用路径曾经只发一段自由文本,一个被复用去干另一个 workstream 的实现子代理于是花了 7 步 / 15 次 read 去重新发现一份主代理手里已有的清单——files 在派发路径上修过一次 (0.3.5/0.3.6),这是把它补回复用这条路径;
  • 参数 compact(可选,默认 false):在投递之前压缩该子代理自己的历史 (走该预设 realm 私有的 compaction 服务——agentPresets.serviceFor(agent, "compaction"), 超时 10 分钟)。只应在「复用会把干扰带进来」时用(四条可判定判据见预设的 「Reuse the stage subagents you already have」小节);代价是抹掉它超出摘要的历史记忆并 放弃前缀缓存。压缩失败 ⇒ 什么都不投递(错误文案明说 inbox 未变,可改用 compact: false 投递);compactNow 返回「没有可压区间」不算失败,投递照常。冷子代理(重启后本进程未唤醒) 无法压缩,唤醒也救不了——子代理答完就回到冷态:两条宿主约束结构性互斥(压缩需要活着的 agent,而活着的 agent 要么正在回合中(compactNow 抛 busy),要么刚被一次投递冷唤醒、inbox 已经满了)。实测本安装 16 次压缩(11 次手动 /compact + 5 次自动)没有一次属于阶段子代理, 所以复用时 compact: true 不可用:同一工作流的真正续轮就接受干扰;如果是新的独立工作流,直接给它派发新子代理(0.4.0 起没有创建名额上限),并说明选了哪一个;
  • 行为:调用宿主原生 subagents.sendMessage(alpha.4 语义 = steer/插入)—— 运行中的子代理在下一个模型步骤就看到该消息(不排进队列等当前回合结束); 子代理已空闲/已结束时会唤醒开新回合处理;
  • 投递方式可配置(设置 → 代码流水线 →「子代理消息投递」):默认固定插入 (sendMessage/steer,运行中最近步骤即收到);切到固定排队后走原生 human-queue 通道(subagents.prompt,当前回合结束后按顺序处理);
  • 资格:与其他阶段工具一致,只对组合了 code-pipeline 预设的 ROOT 代理注入; 子代理身份校验由宿主 lineage 授权(非本代理直属子代理会被拒绝并报错)。
  • 它同时是多轮评审复用的通道:评审第 2 轮起用 pipeline_followup 续用同一个评审 子代理(见下节「多轮评审复用」)——同一个投递通道,child 传该评审子代理的 subagentId。

多轮评审复用(续用同一个评审子代理)

多轮评审(plan → impl ↔ review 里的 review 轮次)不再每轮新开一个评审子代理:第一轮 之后的所有轮次通过已有的 pipeline_followup 续用第一轮那个评审子代理。

这是「同一任务只创建一次」这条统一协议在评审阶段的具体形态:预设的 「Reuse the stage subagents you already have (SAME child, later rounds)」把 plan / impl / review 三阶段都写进同一条协议(同一 workstream/任务第一次用阶段工具派发,之后每一轮都用 pipeline_followup 发给已有的那个子代理;而一个新的独立 workstream/任务一律新派自己的子代理—— 0.4.0 起没有创建总量上限,只有「同时运行数」上限会让新派发等一个名额)。 没有新增任何设置项(复用既有 maxConcurrency,现在它是纯运行并发上限)。

  • 为什么:子代理从空会话起步——每轮新开 subagent_review 都要重新吃一遍「计划 + 完整 diff + 历史结论」,而且新会话没有前缀缓存可命中;续用同一个子代理时,这些都在它的 会话里(前缀命中缓存),新一轮只需投递增量物料。
  • 怎么做(persona 的硬性协议,见预设「Repeat review rounds reuse the SAME reviewer」):
    1. 第 1 轮照常 subagent_review(计划 + 实现摘要 + 完整 diff),并记住它返回的 subagentId —— 那个子代理就是本任务的评审者(写进 todo_write 流程,防上下文压缩丢失);
    2. 第 2 轮起改用 pipeline_followup:child 传该 subagentId(本会话只有一个评审子代理时 可用 child: "review"),message 里写清「这是第几轮 + 完整的新 diff + 每条编号问题的 处理说明 + 回复契约(APPROVED / CHANGES REQUIRED: + 编号问题)」;计划与历轮 diff 不要再传(评审子代理自己还留着,重复传正是复用要省掉的开销);
    3. 结论仍以「完成通知」形式回到本会话,与首轮完全一致——主代理侧流程不变。
  • 边界:
    • 该评审子代理还在跑(结论未到)时不要续发新回合——先等完成通知(与"不要重复派发 进行中的阶段"同一条规则);
    • 新任务用新的 subagent_review,绝不复用别的任务的评审子代理;
    • 多个独立目标并行评审时,每个目标一个评审子代理,按各自的 subagentId 续用;
    • pipeline_followup 报「没有匹配的阶段子代理」时(例如 dsh 重启后插件进程内的派发台账 被清空),退回一次带完整物料(含计划)的 subagent_review 即可——这是回退,不是 「阶段不可用」,不要因此终止任务;但若该阶段已达运行上限(见下节),这次回退也要等一个名额释放——此时先等完成通知、或在后续步骤重试;
    • 宿主侧依据:pipeline_followup 走 subagents.sendMessage,空闲/已 settle 的子代理会被 唤醒成新一轮(宿主 steer 语义:idle driver starts a turn;queue 模式同理排一个新回合), 且再次 settle 时父会话照常收到完成通知。

评审物料只允许写系统临时目录($env:TEMP)

主代理为了把大的变更集从 subagent_review(diff=…) 参数里卸下来,可能用 Out-File 把 diff 写到项目根目录(如 .review_*.diff,已多次实测发生)。 三个阶段工具的 description 现带绝对物料卫生纪律:工作区任何位置 (根目录 / 子目录 / .pipeline-tmp/)都不允许创建任何物料/中间文件 (*.diff、.review_*、变更集文件等);确需落盘时只允许写入 $env:TEMP\dsh-code-pipeline,且必须在本次调用返回前删除。

注意:子代理自己的输入框仍会排队(宿主 subagents.prompt 硬编码 mode: 'continuable',且输入栏对子代理会话关闭了 steering)——这是宿主行为, 插件侧无法改变;改需求请走主对话 → 代理调用 pipeline_followup。

安装

方式一:一行命令安装(推荐,GitHub 分发)

dsh plugin --profile web add github:ErrorLst/dsh-code-pipeline
  • 该命令在 web profile 下执行 pnpm add github:ErrorLst/dsh-code-pipeline;安装成功后 reconcile 会读取包内 dsh.bundle.patch 声明,自动把 @dsh-external/dsh-code-pipeline 追加进 dsh.profile.bundles(无需手动登记)。
  • 重启 dsh web 即挂载生效(bundle 层在启动时组合,客户端 bundle 在启动时扫描)。
  • 预设自动安装 / 自动同步:$DSH_HOME/.agent-presets/code-pipeline 缺失时从包内 preset/code-pipeline/ 拷贝;已存在时,只要包内预设的内容变了(即插件升级)就自动覆盖它, 覆盖前留一份 temp 备份。两次升级之间你对已安装副本的本地改动会被保留(见「预设文件」)。

方式二:本地开发安装

dsh plugin --profile web add link:
# 或:dsh plugin --profile web add 
  • 与方式一相同:dsh plugin add 自动完成依赖安装与 bundle 登记,无需手动编辑 dsh.profile.bundles;默认 profile 名为 web,其他用 dsh plugin --profile add ...。
  • 启动后预设同样自动安装;本仓库以 link: 挂载,改 lib/ 后重启 dsh 生效, 客户端改动刷新页面即可。

卸载

dsh plugin --profile web remove @dsh-external/dsh-code-pipeline

从依赖与 bundle 层移除;预设目录($DSH_HOME/.agent-presets/code-pipeline)不会被 删除,需要时手动删除即可。

预设文件(preset/)

code-pipeline 预设的组合内容(主代理 persona 与流水线协议、Code Mode 展示、 禁用通用 subagent/subagent_fork、delegation 组等)随本仓库在 preset/code-pipeline/ 目录维护(agent.cordis.yml + preset.yml)。

  • 自动安装 / 自动同步(0.4.3 起):插件启动时若发现 $DSH_HOME/.agent-presets/code-pipeline 缺失,会从包内 preset/code-pipeline/ 自动拷贝;若已存在,则比较安装目录里 .dsh-bundle.json 记录的「上次同步指纹」与包内预设的当前指纹(sha256,覆盖 agent.cordis.yml + preset.yml 等全部文件):

    • 指纹一致 → 不动用户文件(两次插件升级之间你对已安装副本的本地改动得以保留);
    • 指纹不一致(插件升级,或首次没有记录)→ 自动用包内预设覆盖已安装副本、写回新指纹, 并把覆盖前的副本备份到 $TMPDIR/dsh-code-pipeline-preset-backup/ --/。 覆盖后设置页的「压缩触发比例」会照常对账写回,不需要手动重设。
  • 手动补装 / 手动回退(自动同步失败,或想拿回某次覆盖前的版本时):

    Copy-Item -Recurse -Force "$PSScriptRoot\preset\code-pipeline" "$env:DSH_HOME\.agent-presets\code-pipeline"
    

    ($env:DSH_HOME 默认 C:\Users\\.dsh;覆盖前的备份路径在同一条启动日志里。)

  • 生效时机:新会话/新子代理生效(dsh 的 standing 挂载按组合文件的变化时间戳 重建);已经在运行的会话不会自动切换——需要换新预设请开新会话。

  • 插件与预设的版本对应:插件保证与仓库内 preset/ 副本一致的那一版预设协同工作, 并在包内预设变化时自动同步(见上)。启动日志里若出现 installed preset ... differs from the bundled copy,说明你在已安装副本上做过的本地改动 与包内不同(设置页写入的压缩比例不计入这个比较)——注意:下一次插件升级会覆盖这些改动。

预设要求

  • 预设中不得再包含静态的 stage-plan / stage-impl / stage-review 行 (由插件注入,避免重名/双重定义)。
  • 其余组成(persona、Code Mode 展示、只读过滤语义、禁用通用 subagent/subagent_fork、禁用 tool-workflow、delegation 组)保持仓库 preset/ 副本的样子。
  • 与上游内置 ptc 同步的宿主行不要删:例如 0.1.5-alpha.2 起新增的 - id: present(@deepseek-ai/dsh-tool-present)——阶段子代理写的文件要靠 它由主代理登记为「本轮交付物」;删掉后模型侧再无交付声明工具(persona 里的 交付要求会指向一个不存在的工具)。
  • 仓库内的 preset/code-pipeline/ 就是唯一维护源:对预设的任何修改请先改这里, 再同步拷贝到 $DSH_HOME\.agent-presets\code-pipeline。

人工闸门(plan 之后)

build flow 的闸门是对话内自然闸门,不用 ask_user_question 弹卡片(卡片不支持 Markdown 渲染,长计划会挤压展示):

  1. plan 阶段返回后,主代理把完整计划以正常 Markdown 回复直接呈现在对话中, 然后结束回合等待用户输入;
  2. 用户下一条消息即闸门答复:批准(approve / 批准 / 同意 / ok / 可以 / 开始 / 没问题 等,且无新增要求)→ 进入实现阶段;其他任何内容视为修订反馈 → 并入计划 重新呈现;修订没有次数上限——每一轮修订都只发生在你给出新方向之后,循环由你控制,直到你批准或明确叫停。

运行规则以 preset/code-pipeline/agent.cordis.yml 的 pipeline protocol 为准。

派发消息整体落盘(单个临时文件)

主代理调用阶段工具时,插件把完整派发消息——prompt/task 与该阶段所有物料字段 (context / plan / constraints / implementationSummary / diff / focus)合并后的 全文——整体写入一个临时文件(os.tmpdir()/dsh-code-pipeline/ 下,文件名带阶段前缀和 UUID),子代理提示中仅保留 written to temp file: — read the WHOLE file with the read tool 引用。子代理只需 read 一次即可拿到全部消息:不会因长 diff 在派发/模型上下文中被截断, 也避免了逐字段多文件的读取负担。

  • 默认全部落盘(config.spillAllFields: true,设置页可关);关闭后回退阈值模式: 仅当消息超过 config.largeFieldLines(默认 100)行时才落盘。
  • review 的 diff 硬校验不变:原始值必须含 @@ 块头(完整补丁文本)——校验在落盘 之前执行,统计摘要 / “见 git show”引用仍被拒绝。
  • 临时文件在启动时自动清理(超过 24 小时的删除)。

长任务与后台派发

阶段工具没有工具级超时(未声明 timeoutMs,不会触发官方 timeout policy);但前台等待 受当前回合/调度生命周期约束,长跑阶段可能被回合边界截断(宿主 run_code 的墙钟默认 120 s、 部署上限 600 s,传 timeoutMs 可顶到部署上限)。

  • 后台模式(默认,推荐):run_in_background 省略/为 true——立即返回 {"kind":"continuable","subagentId":"..."} 并结束回合;阶段子代理独立会话继续运行, 完成后 runtime 自动向本会话发送通知(含结果与最终回复);
  • 前台模式(仅短任务):run_in_background: false——等待阶段结果;注意 run_code 程序的墙钟默认 120 s、部署上限 600 s(传 timeoutMs 可顶到上限), 超过会截断等待并取消子代理,所以只有几分钟内能完成的小任务才用前台;
  • 状态可见:list_agents(running / idle / ready)、send_message 继续子代理; 完成通知里就带子代理的 outcome 与最终回复(没有独立的 history 工具, 所以阶段子代理必须把完整结论写进最终回复),GUI 子代理视图同步展示;
  • 长任务(预计超过当前回合可承受时长)请用后台模式,收到完成通知后再继续下一步。

默认值

所有阶段默认统一走 deepseek-official / deepseek-flash(= DeepSeek-V41-Flash, dsh 0.1.5-rc.1 起宿主 agent-default-model 的默认模型 id;旧 id deepseek-v4-flash 仍在默认目录中,但已不是默认,且宿主目录可被 settings.yaml 的 llm-deepseek.models 收窄——插件默认值必须留在默认目录内):

阶段默认 provider默认 model默认并发上限角色
plandeepseek-officialdeepseek-flash0(不限制)只读,仅规划
impldeepseek-officialdeepseek-flash0(不限制)全工具面,仅实现
reviewdeepseek-officialdeepseek-flash0(不限制)只读,仅审查

已保存过阶段配置的会话不受影响:settings.yaml 的 code-pipeline.stages 里显式 写下的 provider/model 始终优先于这里的默认值。

0.4.0 起 maxConcurrency 只表示「同时运行」的并发上限(默认 0 = 不限制,零回归)。 旧版本那个「同一 (父会话 × 阶段) 已创建(含已结束)总量」闸门已移除:新工作流一律派发 自己的新子代理;只有同一工作流的后续轮次才用 pipeline_followup 续用。

无 fallback 孪生工具:阶段 provider/凭据/启动失败时直接报错并报告,不自动换路由。

read 读取窗口下限(readWidenMinLines)默认 200(0.4.7 起;0.4.5 曾为 2000、0.4.6 曾为 500):本预设代理发出的 read,limit 低于该值会在执行前被 就地拓宽到该值,超过 2000 的治愈为 2000;2000 = 一律整窗,0 = 关闭。 见「read 读取窗口拓宽(一次多读)」。

思考等级(reasoningEffort)

每阶段可在设置页配置「思考等级」。选项按所选模型的实际支持面列出——host 端点通过 llm.resolveModelInfo(provider, model) 读取每个模型的 reasoning.efforts(deepseek 系为 off/low/high/max,GLM-5.3 为 low/high/max); 信息不可用时用兜底交集 [low, high, max]。换 provider/模型时自动重置为「继承默认」, 避免把模型不支持的等级写入配置(运行时对不支持的等级会直接拒绝调用)。

留空 = 继承 provider 路由级默认(如 llm-deepseek.reasoningEffort、 llm-pi-ai 路由的 reasoning)。实现方式:工具派发时给子代理 options 打 stageKey 标记;插件在官方扩展点 agent/request waterfall 中,对命中阶段且已 配置思考等级的子代理注入 reasoningEffort;留空则完全不动调用配置。

每阶段并发上限与并行派发

  • 设置项:Settings → 代码流水线 → 每个阶段卡片的「最大并发子代理数(同时运行)」。 口径是运行上限:按 (父会话 × 阶段) 统计该阶段同时运行(宿主 activity = running) 的子代理数。0 = 不限制(默认)。
  • 按会话独立:上限由每个父会话各自判定——A 会话跑满该阶段不会占用 B 会话的名额; 账本(运行中 + 在途创建)与宿主 listChildren(parent.id) 核对都以父会话为键。跨会话只做 展示用的合计,绝不参与准入判定。
  • 没有「已创建总量」上限(0.4.0 移除):一个新的独立工作流永远可以派发自己的新子代理, 不论这个阶段之前创建过多少个。旧版本用创建总量闸门强制「复用优先」,代价是把新工作流硬塞给 一个已经做过别的 workstream 的冷子代理(无法压缩,每步重发整段历史):实测一轮 63 步 / 9.7M tokens、每步重发约 152k,还只能靠一句「忽略之前的内容」在 prompt 里硬压——上下文删不掉。 现在改由「新工作流派新孩子」这条协议承担:impl 的同一条工作流后续轮次(评审问题、改需求、 墙钟续跑)继续用 pipeline_followup 回到同一个子代理;新的独立工作流直接用本阶段的阶段工具派发。
  • 派发被运行上限拦下怎么办:这是瞬时策略拒绝,不是阶段不可用——等一个子代理结束 (运行名额在子代理结束时释放),然后在后续步骤里把剩余工作流各自派成新子代理;正在运行的 同一工作流孩子可以用 pipeline_followup 插话(不创建)。不要为了避开等待就把新工作流塞给 一个不相干的子代理。
  • 运行闸门的准入判定(两步):
    1. 同步先到先得:用插件账本(运行中 + 本次启动预留)判定,超限立即拒绝; 通过则同步占位。判定必须完全同步——PTC 的 Promise.all 会让同一阶段的多个 调用同时进入 execute,若等 await 之后再判定,两个并发调用会互相把对方 算进名额而双双被拒(开发时实测到这个缺陷,已修)。
    2. 异步核对:再用宿主 subagents.listChildren(parent.id) 的 activity === "running" 核对真实运行数(捕获账本不知道的子代理:重启前派发的、 被 pipeline_followup 唤醒的),偏保守时可以拒绝一个刚准入的调用;同时 用结果修剪账本里已 settle 的条目(自愈)。宿主没有 listChildren 或查询失败 时退回账本,并用 live Agent 的 status === "idle" 修剪。 超限时工具拒绝本次派发,错误信息明确标注「这是瞬时策略拒绝,不是阶段不可用」—— 主代理应等名额释放后在后续步骤重派(同一工作流的后续轮次用 pipeline_followup), 不得按 UNAVAILABLE 规则终止任务,也不要把「复用某个不相干的子代理」当成唯一出路。
  • 动态修改:工具每次调用都读设置,所以保存后下一次派发立即生效,无需重启。 调高立即放开;调低不会中断正在运行的子代理,只是在新派发时按新值拦截,直到 运行数降到新值以下。设置页每 5 秒轮询 /dsh-code-pipeline/status:其中 running / pending 是单会话最多(与 limit 同口径,因为设置页是全局卡片、无法只显示某一个会话), sessions / totalRunning / totalPending 是跨会话合计(仅供诊断)——卡片因此显示 「当前运行(单会话最多)N / 单会话上限 M;共 K 个会话在跑(合计 T)」,不会把跨会话的 合计拿去比上限。created / available 是全部会话的信息字段,端点缺失时优雅降级。
  • 运行数的阶段归属 = 宿主 subagents.listChildren(parent.id) 的当前可见行;归属按优先级判定: 本进程台账 → 活子代理的 options.stageKey → label 的 / 前缀(0.2.0 起阶段工具自动给 description 加该前缀,所以宿主持久面上的 label 也是阶段标记)。重启后,只靠持久面的 label 前缀 / live stageKey 也能把在跑的子代理计入正确阶段(0.4.0 起运行闸门用同一条归属路径, 不再只看本进程台账)。
  • 边界:宿主每个 run_code 程序仍有 maxParallelSubCalls(默认 10)的并行 子调用上限,所以设 20 也不会在一个程序里真正并行超过 10 个;ralph 派发的子 代理不经过阶段工具,不受此限;既没有 / label 前缀也没有 stageKey 的旧子代理 无法回溯归属:它们不计入任何阶段的运行数、也不进已创建清单。
  • 宿主还有一层「同时存活」容量上限(dsh 0.1.6-alpha.2 起):宿主为每个会话(root) 维护一个共享的 continuable 子代理名额池,大小 = subagent.maxActiveSubagents(默认 8, 可在 Settings → 内置插件 → 子代理 调大)。它与本插件的运行闸门互相独立:
    • 它数的是同时存活的子代理(跨阶段、跨本插件,含其它来源的子代理), 子代理结束即释放;
    • 名额用尽时派发会被宿主拒绝(ACTIVATION_LIMIT_REACHED),错误文案由插件改写成 「宿主容量耗尽」的瞬时拒绝(不是阶段不可用):主代理应等待/复用,而不是终止任务;
    • 唤醒一个已 settle 的冷子代理(pipeline_followup)同样需要名额;给仍在运行的子代理 插话不需要,所以名额紧张时优先 steer 运行中的孩子。 结论:maxConcurrency: 0(不限制)只解除了本插件的限制,实际并行度仍受 maxActiveSubagents(默认 8)与 run_code 的 maxParallelSubCalls(默认 10)约束。 /dsh-code-pipeline/status 会返回 hostActiveSubagentLimit 字段(宿主未注册该命名空间时不返回)。
  • 预设侧的并行立场(0.3.0 起反转)+ 「新工作流派新孩子」:code-pipeline 预设的 pipeline protocol 现在是写入串行为默认、并行只用于「读 / 分析 / 评审」,并行 impl 必须同时满足 「文件不重叠 + 真正独立 + 各自可机器校验 + T2 规模」(研究:并行写手各自做隐式决策,结果会冲突, 且智能体数量增加收益递减);运行上限拒绝是瞬时的、不是阶段失败;同一工作流的后续轮次一律用 pipeline_followup 回到那个子代理,而新的独立工作流一律新派自己的子代理——运行名额不够时 等一个释放、在后续步骤重派,绝不把新工作流塞给不相干的 child。

每阶段墙钟预算(超时自动中断 + 收尾报告)

  • 设置项:Settings → 代码流水线 → 每个阶段卡片的「墙钟预算(分钟)」;0 = 不限制(默认)。 口径是该阶段单次派发的最长运行时间,不做跨派发累计。
  • 为什么需要:宿主对子代理没有回合 / 步数 / 时长上限(agent-loop 的 Config 只有 maxParallelToolCalls;dsh-tool-call-timeout-policy 只管单次工具调用),一个跑飞的 impl 只能由模型自己决定停下,于是长时间烧 token、工作区停在半成品。
  • 超时后插件做什么(15 秒一轮巡检账本): 0. 软警告(预算 80%):SOFT_WARN_RATIO = 0.8 处先给仍在运行的子代理插一条 steer 消息(subagents.sendMessage):「预算只剩 X 分钟,开始收尾:做完手上这一处、不要开新工作、只跑必要检查,结束前给一段状态报告」。它在最近一个模型步骤就能看到,多数情况会自己收敛、不必掐;只发一次,失败只记日志、不影响硬路径。子代理不在跑(idle)时不发——steer 对 idle 目标是「开一个新回合」。
    1. 中断:subagents.interrupt(childId, { kind: "ancestor", agent: parent }) —— 只结束 当前回合;Activation、未领取的 inbox、已发布的下级都保留,所以之后仍可用 pipeline_followup 把剩下的活儿交回同一个子代理(前缀还在,命中缓存)。
    2. 索取收尾报告:等它真正停下(有界轮询 ≤ 15 秒)后,经 host-protocol 的 delivery: "queue" 通道排队投递一条自包含指令,要求只输出文本:已完成(含精确文件路径)/ 每处改动的状态(完整 · 半成品)/ 未完成项 / 风险与未验证项 / 建议(续跑 · 拆分 · 回退)。 父代理收到的完成通知因此带一份可用现状,而不是只有 left no closing message。
    3. 收尾回合也有宽限(3 分钟):再超时就第二次中断 —— 硬停,不再收尾(防止“收尾又跑飞”)。
  • 对主代理的语义:预算到点是「被中