[Bug] 子代理默认委派失败:子代理继承的是过期的 base 默认模型,而非父代理实际运行的模型 #4311
epiloguess
started this conversation in
General
Replies: 2 comments
|
我今天被这问题deepseek余额变成负数了~ |
0 replies
|
We independently reproduced this subagent model inheritance bug and implemented a focused source-level fix with full test coverage. Root Cause
Fix & VerificationWe published a clean standalone fix branch against master:
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
[Bug] 子代理(subagent)默认委派失败:子代理继承的模型与主代理实际模型不一致——根因包含
options创建时快照定格、全局agent-default-model被会话级切换污染,且错误被吞没环境
webprofilepackages/host/apiproxy(发布包@deepseek-ai/dsh-host-apiproxy)packages/subagent/subagent(发布包@deepseek-ai/dsh-subagent)packages/subagent/subagent-in-process-driver(发布包@deepseek-ai/dsh-subagent-in-process-driver)packages/core/agent(发布包@deepseek-ai/dsh-agent)packages/subagent/agent-default-model(发布包@deepseek-ai/dsh-agent-default-model)packages/bundle/base(发布包@deepseek-ai/dsh-base)摘要
通过
subagent/subagent_fork或workflow的agent()委派子代理时,默认情况下失败,表现为Error: subagent run failed(或agent()返回null、子代理产出 0 token / contextWindow 不一致)。子代理在首轮 LLM 调用即挂掉——它拿到的模型不是父代理实际运行的模型。复现步骤
dsh web使用默认webprofile(base 默认agent-default-model指向未配置凭证的deepseek-official/deepseek-v4-flash)。subagent工具或workflow的agent("..."),不指定provider/modeloverride。Error: subagent run failed。agent("...", { provider, model })override → 成功。证明执行后端正常,坏的是到达子代理的默认模型选择。
期望行为
父代理可指定子代理模型;未指定时,子代理应使用父代理实际运行的同一模型,而非来自其他来源的硬编码/未知默认。
实际行为
resolveChildAgentOptions继承parent.options快照(packages/subagent/subagent/src/child-agent.ts:68-83)。parent.options定格为 base 默认deepseek-official:selection.currentgetter 的兜底返回defaults.defaultModelSelection()而非agentDefaultModel.currentSelection()(packages/host/apiproxy/src/api-proxy.ts的selectionFor,对应发布包dsh-host-apiproxy/lib/index.js:1697-1706)。installModelSelection在agent/request时按请求注入(packages/core/agent/src/model-selection.ts,对应dsh-agent/lib/index.js:255-303)——这是与options快照完全分离的路径。deepseek-official无凭证)被折叠为stopReason: "error"且无diagnostic(dsh-subagent/lib/index.js:191-212;dsh-subagent-in-process-driver/lib/index.js:124-132),表面只剩subagent run failed。根因分析
1.
resolveChildAgentOptions只继承parent.options创建时快照parent.options是ReactLoopAgent构造时保存的不可变快照(只设一次)。主代理实际模型通过agent/requestwaterfall 在每次 LLM 调用时覆盖,但不回写parent.options。UI 切换后父代理用新模型,子代理却继承旧快照。2. web 入口把
options定格为 base 默认selectionFor的selection.currentgetter 兜底到defaults.defaultModelSelection()(base 硬编码deepseek-official),而非agentDefaultModel.currentSelection()(用户实际的默认/选择)。对照packages/host/headless(发布包dsh-headless/lib/index.js:66-75)正确地用agentDefaultModel.currentSelection()填充options——无头模式子代理大概率正常,web 模式不正常。3.
agent-default-model全局配置被会话级切换污染(更深的根因)selectModelRPC(packages/host/apiproxy/src/api-proxy.ts,对应dsh-host-apiproxy/lib/index.js:2596-2630,第 2614 行)在每次切换模型时既设置会话级selection.current,又调用saveDefaultModelSelection把值持久化进全局settings.yaml的agent-default-model命名空间(packages/subagent/agent-default-model,saveSelection→settings.replace)。→ 名为"默认模型"的全局单值配置,实际是"用户最后一次切换"。在多会话产品里语义错误:会话 A 切模型静默改写全局默认,被会话 B/子代理读取。且 web 入口创建新会话/子代理时并不消费该全局值(用 base 默认),使其既被污染又没被当默认使用。
4. 配置层:
tool-subagent缺agentOptionspackages/bundle/base的cordis.patch.yml(第 313-329 行)中tool-subagent/tool-subagent-fork均未设agentOptions;其 Config 类型虽已支持该字段并会透传,但配置缺失导致子代理无模型兜底。5. 错误被吞(症状放大器)
toStopReason/readResult在出错时不附加diagnostic,使失败几乎无法从表面报错诊断。涉及组件
dsh-host-apiproxyselection.current兜底错(1697-1706);selectModel每次写全局默认(2596-2630, 2614)dsh-subagentoptions快照;吞 diagnosticdsh-subagent-in-process-driverdiagnosticdsh-agentinstallModelSelection按请求注入options分裂dsh-agent-default-modeldsh-baseagent-default-modeldeepseek-official/deepseek-v4-flash默认值来源;tool-subagent缺agentOptions修复建议
resolveChildAgentOptions在parent.options.provider/model缺失时,从agentDefaultModel.currentSelection()读取当前默认作为 fallback(注意:需配合下方 F2/F3,否则在 web 模式下读到的可能是被污染的"最后一次切换"值)。options:创建主代理时,用agentDefaultModel.currentSelection()(感知 settings/覆盖)填充options,与dsh-headless对齐,而非defaults.defaultModelSelection()。selectModel只设会话级selection.current;仅当用户**显式"设为默认"**时才持久化到全局agent-default-model。恢复该 key 的真实语义。tool-subagent配置加agentOptions,但不可硬编码,应从agent-default-model读取或做成模板变量(不同部署默认模型不同)。toStopReason返回"error"时附加diagnostic,使subagent run failed可操作。补充(架构)
两个症状同源:模型选择没有单一事实来源。四套机制重叠共存——
options快照(创建时)、selection.current(会话级/请求时)、请求config(installModelSelection注入)、全局agent-default-modelsettings。仅修resolveChildAgentOptions兜底会暴露"全局默认被污染";仅修selectModel持久化而不修继承链,子代理仍坏。需跨core/agent/subagent/host/apiproxy/agent-default-model协调对齐语义。English Version
[Bug] Subagent delegation fails by default: child inherits a model inconsistent with the parent's actual model — root cause spans a frozen
optionssnapshot, a globally-pollutedagent-default-model, and a swallowed errorEnvironment
webprofilepackages/host/apiproxy(@deepseek-ai/dsh-host-apiproxy)packages/subagent/subagent(@deepseek-ai/dsh-subagent)packages/subagent/subagent-in-process-driver(@deepseek-ai/dsh-subagent-in-process-driver)packages/core/agent(@deepseek-ai/dsh-agent)packages/subagent/agent-default-model(@deepseek-ai/dsh-agent-default-model)packages/bundle/base(@deepseek-ai/dsh-base)Summary
Delegating via
subagent/subagent_forkorworkflow'sagent()fails by default, surfacing asError: subagent run failed(oragent()returningnull, or the child producing 0 tokens / a contextWindow mismatch). The child dies on its first LLM call — it receives a model that is not what the parent actually runs.Steps to reproduce
dsh webwith the defaultwebprofile (baseagent-default-modelpoints at unconfigureddeepseek-official/deepseek-v4-flash).subagent/workflow'sagent("...")without aprovider/modeloverride.Error: subagent run failed.agent("...", { provider, model })with explicit override → succeeds.Proves the execution backend is fine; what's broken is the default model selection reaching the child.
Expected behavior
The parent may specify the child's model; when unspecified, the child should use the same model the parent actually runs on, not a hardcoded/unknown default from another source.
Actual behavior
parent.optionsviaresolveChildAgentOptions(packages/subagent/subagent/src/child-agent.ts:68-83).parent.optionsto the base defaultdeepseek-official: theselection.currentgetter falls back todefaults.defaultModelSelection()instead ofagentDefaultModel.currentSelection()(packages/host/apiproxy/src/api-proxy.ts,selectionFor; publisheddsh-host-apiproxy/lib/index.js:1697-1706).installModelSelection(packages/core/agent/src/model-selection.ts;dsh-agent/lib/index.js:255-303) — a path completely separate from theoptionssnapshot.deepseek-official) is collapsed tostopReason: "error"with nodiagnostic(dsh-subagent/lib/index.js:191-212;dsh-subagent-in-process-driver/lib/index.js:124-132), leaving onlysubagent run failed.Root cause analysis
1.
resolveChildAgentOptionsinherits only the creation-timeparent.optionssnapshotparent.optionsis an immutable snapshot saved in theReactLoopAgentconstructor (set once). The parent's actual model is overridden per LLM call via theagent/requestwaterfall but is never written back toparent.options. After a UI model switch the parent uses the new model while the child inherits the stale snapshot.2. The web entry freezes
optionsto the base defaultselectionFor'sselection.currentgetter falls back todefaults.defaultModelSelection()(base hardcodeddeepseek-official), notagentDefaultModel.currentSelection()(the user's actual default/selection). Contrastpackages/host/headless(dsh-headless/lib/index.js:66-75), which correctly populatesoptionsfromagentDefaultModel.currentSelection()— headless children likely work, web children do not.3. The global
agent-default-modelconfig is polluted by per-session switches (deeper root cause)The
selectModelRPC (packages/host/apiproxy/src/api-proxy.ts;dsh-host-apiproxy/lib/index.js:2596-2630, line 2614) on every switch both sets the session-scopedselection.currentand callssaveDefaultModelSelection, persisting the value into the globalsettings.yamlagent-default-modelnamespace (packages/subagent/agent-default-model,saveSelection→settings.replace).→ The globally-named "default model" actually holds "the user's last switch". In a multi-session product this is semantically wrong: switching in session A silently rewrites the global default read by session B / children. Moreover, the web entry does not consume this global value when creating a new session/child (it uses the base default), so the value is both polluted and unused as a real default.
4. Config layer:
tool-subagentlacksagentOptionsIn
packages/bundle/basecordis.patch.yml(lines 313-329), neithertool-subagentnortool-subagent-forksetsagentOptions. The Config type already supports and forwards the field, but its absence leaves the child with no model fallback.5. Errors are swallowed (symptom amplifier)
toStopReason/readResultattach nodiagnosticon error, making the failure nearly impossible to diagnose from the surface message.Affected components
dsh-host-apiproxyselection.currentfallback wrong (1697-1706);selectModelwrites global default every switch (2596-2630, 2614)dsh-subagentoptionssnapshot; swallows diagnosticdsh-subagent-in-process-driverdiagnosticon errordsh-agentinstallModelSelectionper-request injectionoptionsdsh-agent-default-modeldsh-baseagent-default-model;tool-subagentconfigdeepseek-official/deepseek-v4-flash; missingagentOptionsSuggested fixes
resolveChildAgentOptions, whenparent.options.provider/modelis absent, read the current default fromagentDefaultModel.currentSelection()(note: must be paired with F2/F3 below, otherwise the value read in web mode may be the polluted "last switch").optionscorrectly: when creating the main agent, filloptionsfromagentDefaultModel.currentSelection()(settings/override-aware), aligning withdsh-headless, instead ofdefaults.defaultModelSelection().selectModelshould only set the session-scopedselection.current; persist to the globalagent-default-modelonly on an explicit "set as default" action. Restore the key's true meaning.agentOptionstotool-subagentconfig, but never hardcode — read fromagent-default-modelor use a template variable (default models differ per deployment).diagnosticwhentoStopReasonreturns"error", sosubagent run failedis actionable.Additional note (architecture)
The two symptoms share one root: model selection has no single source of truth. Four overlapping mechanisms coexist —
optionssnapshot (creation-time),selection.current(session/request-time), requestconfig(installModelSelection), and the globalagent-default-modelsettings. Fixing only theresolveChildAgentOptionsfallback exposes the "polluted global default"; fixing onlyselectModelpersistence without the inheritance chain leaves children broken. Coordinated semantic alignment acrosscore/agent/subagent/host/apiproxy/agent-default-modelis required.All reactions