DeepSeek Harness subagent inherits the wrong model: fix

TroubleshootingPublished 2026-09-12Author: DeepSeek Plugin Market
DeepSeek HarnessDSH pluginsubagent model inheritanceunexpected billingreasoningEffort
Subagents on a different model, surprise billing, or rejected delegations in DeepSeek Harness trace to creation-time options snapshot. Fixed in v0.1.2-alpha.1.

DeepSeek Harness subagents run on a different model than the interface shows, quietly generate billing, or get rejected the moment you delegate, and the root cause is the same: a subagent inherits the parent session's options snapshot from its creation moment (provider / model / maxTokens) rather than the live route the parent session is actually using, and newer fields such as reasoningEffort are not carried over at all. It is fixed natively in v0.1.2-alpha.1, which resolves the route from the request-time selection; until then, workarounds such as "start a new session after switching models" and "name the model explicitly in a workflow" apply.

DSH plugin subagent symptoms: unexpected billing and rejected delegations

The same inheritance gap causes incidents in both the billing and the availability direction, and both hide behind a parent session that looks perfectly healthy. What the community measured:

  1. The main model switched, the subagents did not: after the main conversation switched to deepseek-v4-flash (the log shows zero v4-pro requests on the main session after 21:00), all 23 subagent sessions ran on deepseek-v4-pro for 550+ pro requests in total. Pro billing kept climbing while the subagents worked (10.36 CNY to 14.44 CNY), while the chat indicator showed flash (#1472).
  2. Free-versus-metered confusion is the most dangerous case: in a hybrid deployment (official API plus local llama.cpp), the user believed subagents were running locally for free, but every subagent request hit the metered official endpoint. The subagent request header was measured as deepseek-official / deepseek-v4-flash / maxTokens 256000, where the local model should have been local-aeonb / 27B-AEON-Q5 / 65536 (#1581).
  3. Delegation rejected outright by the endpoint: spawn subagents silently drop reasoningEffort, so the request carries no thinking parameter. Against an endpoint whose model always thinks and cannot be disabled (such as z.ai glm-5.3-flash), that returns 400 {"code":"1210", ...}, while the parent tool only sees Error: subagent run failed with no diagnostics at all. subagent_fork, which also runs in-process, completes normally because it clones the full session header including the persisted effort (#4666).
  4. Failures spread: after the parent session escaped to a working model, newly dispatched subagents still inherited the "stopped local route" from the old snapshot and kept reporting Connection error. / code TRANSPORT. The same family includes 401s, burned weekly-quota 429s, empty responses, and broken streams.
  5. It reproduces in both directions (#1581): created on the local model, switched to flash, and subagents stay local; created on flash, switched to the local model, and subagents stay on flash. Both directions prove subagents do not read "what is in use now".

DeepSeek Harness mechanism: the parent.options snapshot and the missing field

The structural cause is that two notions of "the current model" coexist in the same codebase, and subagents read the one that never updates. Layer by layer:

  1. Inheritance reads the snapshot: resolveChildAgentOptions() (packages/subagent/subagent/src/child-agent.ts:68-83, matching dsh-subagent/lib/index.js:501-512 in the published package) copies only parent.options.provider / parent.options.model / parent.options.maxTokens into the child, then spreads the caller-supplied requested on top.
  2. The snapshot is written once at construction: parent.options is assigned in the agent constructor (agent-loop/lib/index.js:354) and no code updates it afterwards. Its content comes from the global default at session creation or resume time (api-proxy's selectionFor to defaults.defaultModelSelection(), reading agent-default-model in settings.yaml).
  3. The live route lives elsewhere: the provider/model/maxTokens actually sent in the current turn is stored in the session's latest request/header event (packages/core/session/src/index.ts:670), and both the web-layer selectionFor() and the system-prompt variables resolve from there. The doc comment on selectionFor states the precedence explicitly: it is resolved on every read, not seeded once.
  4. selectModel never back-fills the snapshot: switching models updates exactly two places, selectionFor(...).current and the global default in settings.yaml, while parent.options keeps the old value. This has a side effect: every model switch also rewrites the global default, which makes the mismatch ("new sessions follow the new model, subagents follow the old snapshot") even harder to reason about.
  5. The broken continuation path calls the same function: continuation.ts both snapshots the same pair into the descriptor and calls resolveChildAgentOptions when actually starting, so one-shot spawn and continuable subagents break together; a fix must touch both copies (source and bundled output).
  6. The reasoning field simply does not exist: v0.1.1-rc.2's AgentOptions (packages/core/agent/src/runtime-types.ts) has no reasoningEffort, and a fresh loop's buildRequest seeds only { provider, model }. spawn subagents do not install installModelSelection and have no persisted header to restore effort from, so the adapter translates "absent" as the provider default, and thinking-only endpoints reject it.
  7. The correct precedence is short to write: treat parent.session.requestHeader()?.config as authoritative, with parent.options only as a fallback for a session that has not issued any request yet; maxTokens should follow the header too, so flash's 256000 and the local model's 65536 stop contaminating each other. The community patch adds one more detail: a maxTokens that the adapter supplied by default rather than the caller specifying it explicitly must not be promoted into an explicit child limit, otherwise the old route's output budget is frozen onto the new one. For continuable creation the value should be resolved once before the first await, so the initial materialization and the persisted descriptor see the same value and a cold resume does not drift back to the creation-time route.

DeepSeek Harness fixed versions, workarounds, and self-checks

v0.1.2-alpha.1 fixes this natively; before that, pick a workaround for your version and verify with a three-header comparison. Specifically:

  1. What the fix does: child-agent.ts now resolves the subagent's provider / model / effort from the request-time selection, with the creation-time options used only as a fallback before the parent session has issued its first request. AgentOptions gained reasoningEffort?: ReasoningEffortId, and the request builder reads this.options.reasoningEffort ?? persistedReasoningEffort. dsh-tool-subagent's schema supports per-call agentOptions and exposes a model-visible reasoning_effort parameter.
  2. Workarounds on the rc line (without changing source): (1) after switching models, start a new session before dispatching subagents, since only a new session rewrites the creation snapshot and resume does not; (2) for multi-subagent work, switch to a workflow and pass an explicit model to agent(); (3) pin role definitions to a full provider/model so a parent switch cannot carry the old snapshot into children; (4) if subagents must follow the current model, a third-party subagent runtime such as pi2dsh with @tintinweb/pi-subagents inherits from the parent by default, resolving the route from ctx.model at creation time.
  3. Self-check with three headers: capture (1) the parent session's current request/header, (2) the first request/header of a fresh spawn subagent running the same bounded task, and (3) the header of a fork subagent. If provider/model match but reasoningEffort appears only in (1) and (3), you are hitting the routing-fidelity gap. Note that a passing fork run is only a control; it does not prove spawn is fixed.
  4. Confirm the version: dsh --version needs to be on or after the alpha line that contains the patch. The community patch plugin previously offered to ≤0.1.1-rc.2 users (propagating effort plus adding a subagent_effort tool) is superseded by the official implementation from 0.1.2-alpha.1 onward and no longer needs to be installed.
  5. Operational advice: do not point agent-default-model at a local service that can be stopped on its own, because that turns the whole channel into a single point of failure. Keep the default on an always-on route and switch per session when you need a local model. Other shapes of subagent connection errors are covered in model connection troubleshooting.
  6. When you are debugging plugin-class problems, install and remove plugins through DSH Plugin Hub under Settings, Plugin Market; it is safer than editing the profile by hand because a failure rolls the manifest back.

DSH plugin troubleshooting notes

Do not trust the UI indicator alone — it shows the parent session's selection, not the route a subagent actually uses, so always judge from the session log's request/header. Six points to keep in mind when debugging a DeepSeek Harness plugin subagent:

  1. Do not trust the UI indicator alone: the model indicator shows the parent session's current selection, not the route the subagents actually use; judge from the session log's request/header.
  2. A billing difference is the first signal: local models are free and the official API is metered, so as soon as both kinds of requests land on the same batch of subagents the bill exposes the problem before any error does.
  3. agent-default-model in settings.yaml cannot reach existing sessions: it only applies when there is no session-level selection.
  4. selectModel rewrites the global default: confirm this chain when debugging, so you do not misread "the global default was changed" as "the subagents picked a model at random".
  5. reasoningEffort is part of the route: when you next see "the same model works in the main session but is rejected for a subagent", investigate route fidelity first.
  6. Observability still needs work: the terminal error detail of a subagent used to be swallowed before reaching the parent tool, leaving only subagent run failed; when you see that message, read the child session log directly.
DSH Plugin Hub plugin market: install and remove subagent plugins, check versions and updates

Sources: Discussion #1472, Discussion #1581, Discussion #4666, Discussion #2006, dsh-v0.1.2-alpha.1 release notes.

FAQ

Why does a DeepSeek Harness subagent use a different model and generate unexpected billing?

In DeepSeek Harness a subagent's model is not inherited from the model the parent session is actually using; it is inherited from the parent.options snapshot taken at the moment the session was created. resolveChildAgentOptions only reads parent.options.provider/model/maxTokens, and that snapshot is written once when the agent is constructed and never updated. In one measured case a session kept dispatching 23 subagent sessions and 550+ requests on v4-pro after the main conversation had switched to flash, moving the bill from 10.36 CNY to 14.44 CNY (source: Discussion #1472).

Why are spawn-backed DSH plugin subagents rejected by thinking-only endpoints (400 / code 1210) while subagent_fork works fine?

In DeepSeek Harness, spawn subagents silently drop reasoningEffort: in v0.1.1-rc.2 AgentOptions has no reasoningEffort field at all, resolveChildAgentOptions forwards only provider/model/maxTokens, in-process children do not install installModelSelection, and a fresh loop has no persisted request/header to restore the value from. The adapter then omits the thinking parameter when effort is absent, so a thinking-only endpoint such as z.ai glm-5.3-flash returns 400/1210. Fork works because it clones the full session header including the persisted effort (source: Discussion #4666).

Subagents in a DeepSeek Harness session still will not follow my model switch. What workarounds are available right now?

In DeepSeek Harness, three workarounds apply: (1) after switching the model, start a new session before dispatching subagents, since only a new session rewrites the creation snapshot and resuming an old one does not; (2) use workflow's agent() and pass an explicit model, because the plain subagent tool had no per-call override in older versions; (3) pin role definitions to a full provider/model so the parent's switch cannot carry the old snapshot into children. In hybrid deployments also make sure agent-default-model does not point at a local service that can be stopped independently (source: Discussion #1581).

Has the DeepSeek Harness subagent model inheritance issue been fixed, and how do I check whether my deployment is affected?

The DeepSeek Harness subagent inheritance bug is fixed natively in v0.1.2-alpha.1: child-agent.ts now resolves the subagent's provider/model/effort from the request-time selection, and the creation-time options only serve as a fallback before the first request. AgentOptions gained reasoningEffort, and tool-subagent supports per-call agentOptions plus a model-visible reasoning_effort parameter. To self-check, compare three headers: the parent session's current request/header, the first request/header of a fresh spawn subagent running the same bounded task, and the header of a fork subagent. If provider/model match but effort appears only on the parent and the fork, you are hitting the routing-fidelity gap. A passing fork run is a control, not proof that spawn is fixed (source: Discussion #4666).

Related Terms

creation-time snapshot
The options (provider/model/maxTokens) written into an agent at construction time and never updated while the parent session runs. Subagent inheritance reads this snapshot, so it can diverge from the model the UI currently shows for a long time.https://github.com/deepseek-ai/deepseek-harness/discussions/1581
request header (the request/header event / live route)
The record of what a session actually sent to the provider in the current turn (session.requestHeader().config). Model-selection logic treats it as the authoritative source, and both the web-layer selectionFor and system-prompt variables resolve "the current model" from it.https://github.com/deepseek-ai/deepseek-harness/discussions/1581
reasoningEffort
The request parameter that controls the model's thinking level (off/low/high/max). It is part of the route, but the older AgentOptions had no such field, so spawn subagents emitted requests without a thinking parameter.https://github.com/deepseek-ai/deepseek-harness/discussions/4666

Sources