ErrorLst/dsh-code-pipeline ↗★ 0
@dsh-external/dsh-code-pipeline
为代码流水线预设动态注入阶段子代理及模型配置的插件 适合使用PTC流水线、需要动态调整各阶段模型和并发限制的用户。
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:ErrorLst/dsh-code-pipeline说明文档
阅读完整 README ↗@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 轮照常
subagent_review(计划 + 实现摘要 + 完整 diff),并记住它返回的subagentId—— 那个子代理就是本任务的评审者(写进todo_write流程,防上下文压缩丢失); - 第 2 轮起改用
pipeline_followup:child传该subagentId(本会话只有一个评审子代理时 可用child: "review"),message里写清「这是第几轮 + 完整的新 diff + 每条编号问题的 处理说明 + 回复契约(APPROVED/CHANGES REQUIRED:+ 编号问题)」;计划与历轮 diff 不要再传(评审子代理自己还留着,重复传正是复用要省掉的开销); - 结论仍以「完成通知」形式回到本会话,与首轮完全一致——主代理侧流程不变。
- 第 1 轮照常
- 边界:
- 该评审子代理还在跑(结论未到)时不要续发新回合——先等完成通知(与"不要重复派发 进行中的阶段"同一条规则);
- 新任务用新的
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 渲染,长计划会挤压展示):
- plan 阶段返回后,主代理把完整计划以正常 Markdown 回复直接呈现在对话中, 然后结束回合等待用户输入;
- 用户下一条消息即闸门答复:批准(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 | 默认并发上限 | 角色 |
|---|---|---|---|---|
| plan | deepseek-official | deepseek-flash | 0(不限制) | 只读,仅规划 |
| impl | deepseek-official | deepseek-flash | 0(不限制) | 全工具面,仅实现 |
| review | deepseek-official | deepseek-flash | 0(不限制) | 只读,仅审查 |
已保存过阶段配置的会话不受影响:
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插话(不创建)。不要为了避开等待就把新工作流塞给 一个不相干的子代理。 - 运行闸门的准入判定(两步):
- 同步先到先得:用插件账本(运行中 + 本次启动预留)判定,超限立即拒绝;
通过则同步占位。判定必须完全同步——PTC 的
Promise.all会让同一阶段的多个 调用同时进入execute,若等await之后再判定,两个并发调用会互相把对方 算进名额而双双被拒(开发时实测到这个缺陷,已修)。 - 异步核对:再用宿主
subagents.listChildren(parent.id)的activity === "running"核对真实运行数(捕获账本不知道的子代理:重启前派发的、 被pipeline_followup唤醒的),偏保守时可以拒绝一个刚准入的调用;同时 用结果修剪账本里已 settle 的条目(自愈)。宿主没有listChildren或查询失败 时退回账本,并用 live Agent 的status === "idle"修剪。 超限时工具拒绝本次派发,错误信息明确标注「这是瞬时策略拒绝,不是阶段不可用」—— 主代理应等名额释放后在后续步骤重派(同一工作流的后续轮次用pipeline_followup), 不得按 UNAVAILABLE 规则终止任务,也不要把「复用某个不相干的子代理」当成唯一出路。
- 同步先到先得:用插件账本(运行中 + 本次启动预留)判定,超限立即拒绝;
通过则同步占位。判定必须完全同步——PTC 的
- 动态修改:工具每次调用都读设置,所以保存后下一次派发立即生效,无需重启。
调高立即放开;调低不会中断正在运行的子代理,只是在新派发时按新值拦截,直到
运行数降到新值以下。设置页每 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 前缀 / livestageKey也能把在跑的子代理计入正确阶段(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 目标是「开一个新回合」。- 中断:
subagents.interrupt(childId, { kind: "ancestor", agent: parent })—— 只结束 当前回合;Activation、未领取的 inbox、已发布的下级都保留,所以之后仍可用pipeline_followup把剩下的活儿交回同一个子代理(前缀还在,命中缓存)。 - 索取收尾报告:等它真正停下(有界轮询 ≤ 15 秒)后,经 host-protocol 的
delivery: "queue"通道排队投递一条自包含指令,要求只输出文本:已完成(含精确文件路径)/ 每处改动的状态(完整 · 半成品)/ 未完成项 / 风险与未验证项 / 建议(续跑 · 拆分 · 回退)。 父代理收到的完成通知因此带一份可用现状,而不是只有left no closing message。 - 收尾回合也有宽限(3 分钟):再超时就第二次中断 —— 硬停,不再收尾(防止“收尾又跑飞”)。
- 中断:
- 对主代理的语义:预算到点是「被中