DSH plugin 子代理用错模型?subagent 模型继承、推理档配置与排查方法完整解析
DeepSeek Harness 的子代理跑在与界面显示不同的模型上、悄悄产生计费,或一委派就被端点拒绝,根因是同一条:子代理继承的是父会话创建时刻的 options 快照(provider / model / maxTokens),而不是父会话当前实际使用的实时路由,reasoningEffort 这类新字段更是完全不传导。 已在 v0.1.2-alpha.1 原生修复(按 request-time selection 解析),在此之前可用「切模型后开新会话」「workflow 显式指定模型」等办法规避。
DSH plugin 子代理的两种表现:意外计费与委派被拒
同一个继承缺口在计费与可用性两个方向上都能造成事故,而且都以「父会话看起来一切正常」为掩护。 社区实测:
- 主模型切了,子代理没切:主对话切到
deepseek-v4-flash后(日志显示 21:00 之后主会话 0 条 v4-pro 请求),23 个子代理会话全部走deepseek-v4-pro,累计 550+ 条 pro 请求;子代理工作期间 pro 计费持续增长(10.36 CNY → 14.44 CNY),而对话框指示器显示的是 flash(#1472)。 - 免费/收费混淆最危险:混合部署(官方 API + 本地 llama.cpp)里,用户以为子代理在本地免费跑,实际每个子代理请求都命中官方计费端点——实测子代理请求头是
deepseek-official / deepseek-v4-flash / maxTokens 256000,本地模型应为local-aeonb / 27B-AEON-Q5 / 65536(#1581)。 - 委派被端点硬拒:
spawn出来的子代理会静默丢弃reasoningEffort,请求不带思考参数;对「模型总是思考、不允许关闭」的端点(如 z.aiglm-5.3-flash)直接返回400 {"code":"1210", ...},而父工具只看到一句Error: subagent run failed,没有任何诊断信息。同为进程内运行的subagent_fork因为克隆了完整会话头(含持久化 effort)而正常完成(#4666)。 - 故障扩散:父会话切到可用模型逃生后,新派出的子代理仍从旧快照继承「已停掉的本地路由」,继续报
Connection error. / code TRANSPORT;同一家族还有 401、429 周配额被烧、空响应与流中断等记录。 - 方向可以双向复现(#1581):创建时是本地模型 → 切到 flash → 子代理仍是本地;创建时是 flash → 切到本地模型 → 子代理仍是 flash。两种方向都说明子代理看的不是「现在用什么」。
DeepSeek Harness 机制:parent.options 快照与字段缺失
结构性原因在于同一套代码里并存了两个「当前模型」的概念,而子代理读的是那个永不更新的。 逐层拆解:
- 继承读的是快照:
resolveChildAgentOptions()(packages/subagent/subagent/src/child-agent.ts:68-83,对应发布包dsh-subagent/lib/index.js:501-512)只把parent.options.provider/parent.options.model/parent.options.maxTokens复制给子代理,最后再展开调用方传入的requested。 - 快照只在构造时写一次:
parent.options在 Agent 构造函数里赋值(agent-loop/lib/index.js:354),此后没有任何代码更新它;它的内容来自会话创建/恢复时的全局默认(api-proxy的selectionFor→defaults.defaultModelSelection(),读settings.yaml的agent-default-model)。 - 实时路由在另一处:当前轮真正发出去的 provider/model/maxTokens 存在会话的最新
request/header事件里(packages/core/session/src/index.ts:670),Web 层的selectionFor()与系统提示词变量都从这里解析;selectionFor的文档注释明确写着优先级「每次读取时解析,而不是只播种一次」。 selectModel不回头补快照:切换模型时只更新了两处——selectionFor(...).current与写入settings.yaml的全局默认——parent.options保持旧值。这还带来一个副作用:每次切模型都会改全局默认,于是「新会话跟随新模型、子代理仍跟旧快照」的错位更难理解。- 断链的续传路径也走同一函数:
continuation.ts既把同一对值快照进 descriptor,又在真正启动时调用resolveChildAgentOptions;因此一次性spawn与可续传子代理一起中;改的时候两份拷贝(源码与打包产物)都要改。 - 理由字段根本不存在:v0.1.1-rc.2 的
AgentOptions(packages/core/agent/src/runtime-types.ts)没有reasoningEffort字段,新 loop 的buildRequest也只播种{ provider, model };spawn子代理不安装installModelSelection,没有持久化 header 可以复原 effort,适配器便把「缺失」翻译为 provider 默认 → 强制思考端点拒绝。 - 正确的优先级写起来很短:以
parent.session.requestHeader()?.config为准,parent.options只在该会话尚未发出任何请求时兜底;maxTokens同样跟随 header,避免 flash 的 256000 与本地模型的 65536 相互串味。社区补丁还补了一个细节:request/header中由适配器默认提供(而非显式指定)的maxTokens不应被提升为子代理的显式上限,否则会把旧路由的输出预算冻结进新路由;可续传创建时应在首个await之前一次性解析,让初始物化与持久化 descriptor 拿到同一份值,避免冷恢复时漂回创建时路由。
DeepSeek Harness 已修版本、规避与自查做法
v0.1.2-alpha.1 已原生修复;在此之前按所处版本选择规避,并用「三个 header 对比」自查。 具体如下:
- 修复内容:
child-agent.ts改为按 request-time selection 解析子代理的provider/model/effort,创建时options只作为「父会话尚未发出首个请求」之前的兜底;AgentOptions新增reasoningEffort?: ReasoningEffortId,请求构建器按this.options.reasoningEffort ?? persistedReasoningEffort取值;dsh-tool-subagent的 schema 支持 per-callagentOptions,并暴露模型可见的reasoning_effort参数。 - rc 线规避(不改源码):① 切完模型后开一个新会话再派子代理——新会话会重新写入创建快照,
resume旧会话不会;② 多子代理任务改用workflow的agent()显式指定model;③ 把角色定义钉死到完整provider/model,父会话切模型就不会把旧快照带进子代理;④ 需要子代理跟随当前模型时,可用第三方子代理运行时(如pi2dsh+@tintinweb/pi-subagents,默认策略是 inherit parent,按创建当刻的ctx.model解析路由)。 - 自查用三 header 对比:抓①父会话当前
request/header、②同一有界任务新spawn子代理的首条request/header、③fork子代理的 header。若 provider/model 一致而reasoningEffort只在①③出现,就命中了路由保真缺口。注意fork跑通只是对照组,不能证明spawn已修好。 - 确认版本:
dsh --version落在包含补丁的 alpha 线之后即可;此前为 ≤0.1.1-rc.2 用户提供的社区 DeepSeek插件(补 effort 传导 + 增加subagent_effort工具)在 ≥ 0.1.2-alpha.1 已被官方实现取代,无需再装。 - 运维建议:不要把
agent-default-model指向一个可以被单独停掉的本地服务——那样整条通道都变成单点故障;默认用常在线路由,需要本地模型时按会话切换。子代理连接类报错的其它形态见 模型连接排查。 - 排查 DSH插件 类问题时,用 DSH Plugin Hub 的「设置 → 插件市场」装卸插件,比手工改 profile 更安全(失败会回滚 manifest)。
DSH plugin 排查注意事项
别只盯 UI 显示——模型指示器展示的是父会话当前选择,不代表子代理实际用的路由,判断一律以会话日志里的 request/header 为准。 六条要点:
- 别只盯 UI 显示:模型指示器展示的是父会话当前选择,不代表子代理实际用的路由;判断要用会话日志里的
request/header。 - 计费差异是第一个信号:本地模型免费、官方 API 计量,一旦两者的请求落到同一批子代理上,账单会先于报错暴露问题。
settings.yaml的agent-default-model管不到已存在的会话:它只在没有会话级选择时生效。selectModel会改全局默认:排查时先确认这条链,避免把「全局默认被改」误判成「子代理乱选模型」。reasoningEffort属于路由的一部分:以后遇到「同一个模型,主会话能跑、子代理被拒」这类现象,先按路由保真来查。- 可观测性仍待补:子代理终态的错误详情过去没有回传给父工具,只有一句
subagent run failed;遇到这类报错请直接查子会话日志。

来源:Discussion #1472、Discussion #1581、Discussion #4666、Discussion #2006、dsh-v0.1.2-alpha.1 release notes。
常见问题
DeepSeek Harness 子代理的模型不是从父会话当前实际使用的模型继承的,而是从父会话创建时刻的 parent.options 快照继承的——resolveChildAgentOptions 只读 parent.options.provider/model/maxTokens,而这份快照在 Agent 构造时写入一次、之后永不更新。实测一个会话在主对话切到 flash 后仍派出 23 个会话、550+ 次请求全走 v4-pro,费用从 10.36 CNY 涨到 14.44 CNY(来源:Discussion #1472)。
DeepSeek Harness 的 spawn 子代理会静默丢弃 reasoningEffort:v0.1.1-rc.2 的 AgentOptions 根本没有 reasoningEffort 字段、resolveChildAgentOptions 只转发 provider/model/maxTokens,进程内子代理又不安装 installModelSelection、新 loop 没有持久化 request/header 可恢复该值,适配器在 effort 缺失时省略思考参数,于是强制思考的端点(如 z.ai glm-5.3-flash)返回 400/1210。fork 之所以正常,是因为它克隆了包含持久化 effort 的完整会话头(来源:Discussion #4666)。
DeepSeek Harness 子代理想立刻跟随新模型,可先用三条规避:① 切完模型后开一个新会话再派发子代理(新会话会重新写入创建快照,resume 旧会话不会);② 用 workflow 的 agent() 显式指定 model(普通 subagent 工具在旧版本没有 per-call 覆盖);③ 需要精确固定路由时,把角色定义钉死到完整 provider/model。混合部署里还要注意 agent-default-model 不要指向可被单独停掉的本地服务(来源:Discussion #1581)。
DeepSeek Harness 已在 v0.1.2-alpha.1 原生修复这个问题:child-agent.ts 改为按 request-time selection 解析子代理的 provider/model/effort,创建时 options 只作首个请求之前的 fallback;AgentOptions 新增 reasoningEffort,tool-subagent 也支持 per-call agentOptions 与模型可见的 reasoning_effort 参数。自查用「三个 header 对比」:抓父会话 request/header、同一个有界任务的新 spawn 子代理首条 request/header、以及 fork 子代理 header——若 provider/model 一致但 effort 只在父与 fork 上出现,就命中了路由保真缺口;fork 跑通只是对照,不能证明 spawn 已修(来源:Discussion #4666)。
相关术语
- creation-time snapshot(创建时快照)
- creation-time snapshot 是 Agent 在构造时写入的 options(provider/model/maxTokens),在父会话运行期间从不被更新;子代理继承逻辑读的就是这份快照,因此与 UI 当前所选模型可能长期不一致。— https://github.com/deepseek-ai/deepseek-harness/discussions/1581
- request header(请求头事件 / 实时路由)
- request header 是会话在当前轮实际发往 provider 的配置记录(session.requestHeader().config),是模型选择机制认定的权威来源;Web 层的 selectionFor 与系统提示词变量都从这里解析「当前模型」。— https://github.com/deepseek-ai/deepseek-harness/discussions/1581
- reasoningEffort(推理力度)
- reasoningEffort 是控制模型思考档位(off/low/high/max)的请求参数。它属于「路由」的一部分,但旧版 AgentOptions 没有该字段,spawn 子代理因此发出不带思考参数的请求。— https://github.com/deepseek-ai/deepseek-harness/discussions/4666
来源
- deepseek-harness Discussion #1472:子代理继承会话创建时的 options.model 而非 UI 当前所选模型(导致 v4-pro 意外计费)· deepseek-ai(GitHub Discussions)
- deepseek-harness Discussion #1581:子代理继承父会话创建时模型快照(含双向 API 复现与 request/header 优先级的修复方案)· deepseek-ai(GitHub Discussions)
- deepseek-harness Discussion #4666:spawn 子代理丢弃 reasoningEffort,强制思考端点拒绝所有委派· deepseek-ai(GitHub Discussions)
- deepseek-harness Discussion #2006:子代理未正确继承父 Agent 模型配置(配置层 agentOptions 缺失)· deepseek-ai(GitHub Discussions)
- deepseek-harness release notes dsh-v0.1.2-alpha.1(子代理按 request-time selection 解析 provider/model/effort)· deepseek-ai(GitHub Releases)