Bug: Subagents inherit the parent session's creation-time model snapshot instead of the model the session actually uses #1581
Replies: 5 comments
|
Additional evidence: same root cause reported independently by other users This bug has been hit independently by at least three users in the community, all pointing to the same mechanism:
This confirms the report is not an edge case: in any multi-provider deployment, subagents can silently run on a different (and possibly metered) model than the parent session actually uses. Please consider fixing per Option A (request-header precedence) or an equivalent approach. Related: #117 |
|
I reproduced this against current master 47f9438 and prepared a tested patch:
The patch gives the latest logged Session request/header precedence for provider and model, with parent.options as the fallback before the first parent request. Explicit child agentOptions still have final precedence. One important refinement to the suggested Option A is maxTokens ownership. If request/header marks maxTokens through adapterDefaults.maxTokens, copying that value into child AgentOptions promotes a route-specific adapter default into an explicit cap. The patch therefore inherits an explicit effective maxTokens but omits an adapter-owned default so the selected child adapter can materialize its own value. Continuable creation resolves the child configuration once before its first await. Initial materialization and the durable descriptor receive provider/model from the same value, so cold resume cannot drift back to the creation-time route if the parent changes later. Verification completed:
|
|
@yha9806 Excellent work — thank you for reproducing and landing this fix! 🎉 I reviewed your patch (
The two new regression tests (route-switch inheritance + cold-resume snapshot) cover exactly the failure modes from the two-way reproduction in the report. Hoping the maintainers pick this up for the next release. 🙏 |
|
Real-world production incident confirming Impact #3 (connection failures) A production incident occurred today on the same hybrid deployment (Feishu/Lark channel → DSH → local llama.cpp):
Root cause chain: This is exactly Impact #3 from the report, with two aggravating factors this bug contributes:
Also relevant beyond this bug: pointing the deployment-wide default ( |
|
Correction to my previous comment — clarifying the relationship precisely My earlier comment ("Real-world production incident confirming Impact #3") was imprecise. To be accurate: The incident itself is NOT caused by this bug. The Feishu session was directly calling the local model service ( What this bug does add (the accurate scope): with creation-snapshot inheritance, after the parent escapes to a working route (e.g. switching to the official API), any subagent spawned still inherits the dead local route from Separate operational note (not this bug): pointing |
Uh oh!
There was an error while loading. Please reload this page.
Bug Report: Subagents inherit the parent session's creation-time model snapshot instead of the model the session actually uses
Summary
After switching the model of a session via the web UI, subagents spawned from that session still run on the model that was the global default when the session was created, instead of the model the parent session is actually using. Switching a model updates both the in-session selection and the global default (
settings.yaml), but the subagent inheritance logic reads a one-time creation snapshot (parent.options) that is never updated, so the two diverge.Steps to reproduce
deepseek-official / deepseek-v4-flash(agent-default-modelinsettings.yaml).S(its creation snapshot is thereforedeepseek-v4-flash).Sto a local modellocal-aeonb / 27B-AEON-Q5(invokessession.selectModel).Sis now actually talking to the local model (works as expected).Svia thesubagenttool.request/headerevent carriesconfig.provider = "deepseek-official",config.model = "deepseek-v4-flash",maxTokens = 256000(DeepSeek official provider defaults), not the local model'slocal-aeonb / 27B-AEON-Q5 / 65536.Expected behavior
The subagent should inherit the model the parent session is currently using (
local-aeonb / 27B-AEON-Q5), because:resolveChildAgentOptions's own doc comment states the child "inherits the parent's provider/model route";Actual behavior
The subagent uses the model snapshot taken when the parent session was created, even though the parent switched models afterwards and has been running on the new model.
Observed evidence
Two-way reproduction via the dsh web
session.*API (2026-08-15)Reproduction A (creation = local → switch to flash → subagent still local)
session-020f3838, default model = local-aeonbsession.selectModelto deepseek-v4-flashdeepseek-official / deepseek-v4-flash / 256000(switch took effect)session.prompt)local-aeonb / 27B-AEON-Q5 / 65536❌ does not follow the switchReproduction B (creation = flash → switch to local → subagent still flash; the original user scenario)
session-f4284b40, default model = deepseek-v4-flashsession.selectModelto local-aeonblocal-aeonb / 27B-AEON-Q5 / 65536(switch took effect)session.prompt)deepseek-official / deepseek-v4-flash / 256000❌ does not follow the switchSide-effect observed: every
session.selectModelcall also writes the selected model intoagent-default-modelinsettings.yaml(switching a model changes the global default);settings.yamlchanged in real time during reproduction and was restored tolocal-aeonbafterwards.Root cause
1. Subagent inheritance source: a static creation snapshot
@deepseek-ai/dsh-subagent/lib/index.js:501-512parent.optionsis assigned once in the Agent constructor (@deepseek-ai/dsh-agent-loop/lib/index.js:354:this.options = options) and no code updates it afterwards.2. What the creation snapshot contains: the global default at that moment
@deepseek-ai/dsh-host-apiproxy/lib/index.js:1708-1714(invoked on session create/resume, lines
2164and2152).3. Switching a model updates two things, but never
parent.options@deepseek-ai/dsh-host-apiproxy/lib/index.js:2694-2706(session.selectModel)4. Documented intent contradicts the implementation
selectionForin@deepseek-ai/dsh-host-apiproxy/lib/index.js:1741-1750documents the intended precedence:i.e. the design philosophy is dynamic-first: the authoritative source of a session's current model is its latest request header (
parent.session.requestHeader()?.config). ButresolveChildAgentOptionsreads the staticparent.optionsinstead, so the same codebase maintains two inconsistent notions of "current model":local-aeonb / 27B-AEON-Q5(the model actually in use)parent.options= creation snapshot =deepseek-v4-flashImpact
selectModelalso overwrites the global default amplifies the confusion: after a switch, new sessions follow the switched model while subagents still follow the old snapshot.Real-world consequences in hybrid deployments (official API + local models)
This report comes from a hybrid deployment (DeepSeek official API coexisting with local llama.cpp models). In this scenario the bug causes:
deepseek-official / deepseek-v4-flash / maxTokens 256000), incurring unexpected cost. In the reported usage, large batches of subagent tasks (e.g. multi-chapter novel generation) all consumed official quota.selectModelwrites the global default on every switch, combined with the subagent snapshot mismatch, new sessions and subagents can end up on different lines (local vs official) as the user switches between sessions; cost and behavior become unpredictable and cannot be corrected intuitively from the UI (a new session is required).Suggested fix
Option A (recommended): prefer the parent's actually-used model, fall back to the creation snapshot
Modify
resolveChildAgentOptions(@deepseek-ai/dsh-subagent/lib/index.js:501-512).parent.options.provider ?? logged?.provider, which does NOT fix this bug??only falls back whenparent.optionsis empty, but the bug scenario is precisely whenparent.optionsholds a stale value (e.g. flash), so??always resolves to the stale value. The request header (actually-used model) must take precedence, with the snapshot as fallback — not the other way around:Key points:
logged(request header) takes precedence: after the parent switches models and actually uses the new one, subagents follow the switched model;parent.options(creation snapshot) only serves as a fallback when the session has never issued a request;maxTokensfollows the request header too, avoiding mismatches (e.g. flash 256000 vs local 65536);parent.session.requestHeader()is a public API of@deepseek-ai/dsh-session(Session.requestHeader());parentis an Agent (with.session), so it is directly usable;...requestedis still spread last, so explicit provider/model overrides from the caller keep their precedence.Option B (alternative): update
parent.optionsinsideselectModelIn
session.selectModel(dsh-host-apiproxy/lib/index.js:2701), besides updatingselectionFor(...).currentand the global default, also updatefound.agent.options.provider/model/maxTokens.Note:
options.provider/modelis also read by system-prompt variables (dsh-agent-loop/lib/index.js:1001-1002:ctx.systemPrompt.variable("provider"/"model")); updating it will also change the model name in the system prompt (likely desired), but makingoptionsmutable conflicts with the "creation snapshot" semantics and other paths readingparent.optionsneed evaluation.Verification after the fix
Re-run both API reproductions above (Reproduction A / B); after the fix the expected results are reversed:
Notes
request/headerevents as direct evidence).{"provider": "deepseek-official", "model": "deepseek-v4-flash", "maxTokens": 256000}(old-session spawn) vs{"provider": "local-aeonb", "model": "27B-AEON-Q5", "maxTokens": 65536}(new-session spawn).All reactions