DeepSeek Harness agents.resume missing {{model}}: how to fix

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek HarnessDSHagents.resumeagentOptionssystem promptsession resume
DeepSeek Harness agents.resume without agentOptions fails every turn on {{model}}. Pass model and provider, read the session header, or add a binding fallback.

Calling agents.resume({ resumeSessionId }) from a script restores the session, but every turn then fails immediately with prompt variable "{{model}}" has no value, and turn/end reports reason.kind as error. The session itself is intact and the history is all there. What is broken is that the agent does not know which model it should use.

DeepSeek Harness (DSH) binds {{model}} and {{provider}} during prompt assembly from the agent's live options, not from the session history. agentOptions is optional in the agents.resume signature, and omitting it fills in nothing. This article covers the criterion, the mechanism, and three remediation paths, then adds an early-failure option.

DeepSeek Harness: confirm the resume path, not the prompt

The test is: the same session works in the UI, fails every turn after a programmatic resume, and the error names only a template variable. Four checks:

  1. Open the same session in the UI, pick a model once, and send a message. Expected: a normal reply, which proves the session data is fine.
  2. Resume it in a script with agents.resume({ resumeSessionId }) and no agentOptions. Expected: the first message fails with {{model}} has no value.
  3. Read the turn/end event. Expected: reason.kind is error, not aborted or completed.
  4. Add agentOptions: { model } to the resume call and retry. Expected: the error disappears, which confirms the resume arguments are the cause.

The confusing part is that the error looks like a template problem: it names {{model}}, and the template is built in and untouched. The interpolation has no value; the syntax is fine.

DeepSeek Harness variable binding and minimal reproduction

Both model and provider are read from agent.options during request assembly, so a resume that omits them leaves the bindings empty. According to the community source review in the thread, the chain is:

  1. The agent loop registers the model variable with a getter that reads context.agent?.options.model.
  2. The provider variable reads from the same options object.
  3. A normal agent creation fills both fields through agentOptions.
  4. agents.resume allows agentOptions to be omitted, and omitting it leaves no model on the agent.
  5. Any prompt that references {{model}} throws under strict interpolation, and the turn ends at once.

The blast radius is wider than scripts: headless runners, startup-resume plugins, and automation pipelines all hit it whenever they resume without a model. The reporter's startup-resume plugin restored an interrupted target session and then failed every turn until the model was reselected in the UI. If you maintain one, check the host version and compatibility notes on DSH Plugin Hub before upgrading.

The UI applies a default and records the selection; the programmatic call does neither. That difference is part of the root cause, not a UI quirk:

  1. The UI restore path carries defaults, so the agent's options are populated before the first request.
  2. Selecting a model in the UI writes it into the agent's live options.
  3. agents.resume documents agentOptions as optional and performs no default fill.
  4. Expected: the same session is usable in the GUI and fails in a script until the two paths agree on a default.

You need one persisted session and three lines of code; the two conditions that matter are a fresh process and no agentOptions. Five steps:

  1. Send a message in any session and confirm it is persisted to disk. Expected: it is still listed after a restart.
  2. Quit DSH completely and start a clean process. Expected: no agent instances survive.
  3. Call agents.resume({ resumeSessionId }) without agentOptions. Expected: the call itself returns successfully.
  4. Send a message to the resumed agent or run one turn. Expected: it fails immediately with {{model}} has no value.
  5. Repeat with agentOptions supplied. Expected: the turn completes, which closes the comparison.

DSH plugin path 1: pass agentOptions on resume

The direct fix is to pass the model and provider together, because the call site already knows which model it wants. This suits scripts, batch jobs, and headless runners.

  1. Add agentOptions to the resume call with at least model, and provider when the route needs it.
  2. Make sure the model name matches the endpoint's capabilities; do not use a placeholder string.
  3. Run one short turn to verify. Expected: no missing-variable error, and the reply matches the selected model.
  4. Wrap the call in a helper function so every resume site uses it. Expected: new entry points cannot forget the field.
ts
const agent = await agents.resume({
  resumeSessionId,
  agentOptions: { model: 'deepseek-chat', provider: 'deepseek-official' },
})

DeepSeek Harness path 2: restore the model from the session header

When you resume someone else's session or a historical job, the correct value should come from the last recorded model choice, not from the caller's guess. This is closer to what the UI does.

  1. Read the session metadata and find the last model or provider selection event. Expected: a model name that matches what was in use.
  2. Build agentOptions from those values and call agents.resume. Expected: the resumed agent matches the pre-interruption state.
  3. When the session has no record, fall back to the profile default and log that it is a fallback. Expected: no silent use of the wrong model.
  4. Add tests for three inputs: no record, a valid record, and a corrupt record. Expected: all three behave deterministically.

DeepSeek Harness path 3: add a fallback in the binding

The root fix is to make the binding layer resolve options.model from the session record or the configured default instead of throwing. The fallback value must be truthful, or the prompt will describe the wrong model.

  1. Give the model getter a candidate order: live options, then the last session selection, then the configured default. Expected: it throws only when all three are empty.
  2. Use the same order for provider. Expected: no half-filled state where the model resolves but the provider does not.
  3. Log a clear line when the fallback fires, including the session id and the source of the value. Expected: the fallback is visible during debugging.
  4. Add tests for model only, provider only, and neither. Expected: the first two proceed, the third returns a readable error.
  5. Review how the prompt uses the model name. Expected: self-description and tool selection do not drift because of a fallback.

If your team prefers to fail fast, validate the model at the agents.resume entry point and reject the call with a parameter error instead of failing every turn later. Four steps:

  1. Check whether agentOptions.model is empty at the resume entry. Expected: a missing model is rejected immediately.
  2. Write an error that says resuming needs a model or agentOptions, not just a template variable. Expected: the caller can act on it.
  3. Keep the automatic restore from Path 2 as the default and enable strict mode only on request. Expected: both kinds of caller are served.
  4. Give the early-failure path a stable error code instead of reusing the template error text. Expected: callers can branch on the code.

DeepSeek Harness boundaries and caveats

Most of these traps come from treating a missing model as a template bug; fix the binding and keep the fallback honest.

  1. Filling in only model may surface the next error. provider comes from the same options object, so handle both.
  2. Do not probe the model by sending a trial message after resume. A wrong model still costs tokens and context.
  3. The thread has no official fix version. The issue still reproduces on 0.2.1-rc.2; a reply notes that the tree already contains a reusable fallback, so reuse it rather than writing another.
  4. The gap between UI and programmatic resume is part of the root cause. After the fix, both paths should produce the same defaults. For the related inheritance problem, see subagent model inheritance.

Source: Discussion #10208, deepseek-ai/deepseek-harness.

FAQ

Why does every turn fail with {{model}} after a programmatic DeepSeek Harness resume?

DeepSeek Harness is not missing a template variable; the resume call is missing model options. The agent loop binds model and provider from the live agent options while assembling the prompt. When agents.resume omits agentOptions those two fields are empty, so any built-in prompt that references {{model}} throws during assembly and the turn ends as an error. Supply agentOptions, or make the resume path read the model from the session record.

Why does picking a model in the DeepSeek Harness UI recover the session while a scripted resume fails?

DeepSeek Harness applies defaults in the UI restore path and writes the selected model into the agent options. The programmatic path treats agentOptions as optional and fills in nothing. The two paths disagree about which fields are required, so the same session works in the GUI and fails every turn in a script.

Is adding agentOptions.model to DeepSeek Harness enough, or will another variable fail next?

DeepSeek Harness reads model and provider from the same live options object, so another variable can fail next. provider is read from that object too, and a prompt that references it may fail after model is filled. Pass the model and the provider the session needs together instead of filling a single field.

The DeepSeek Harness thread warns that an arbitrary model makes the prompt lie. What value should I use?

DeepSeek Harness should use the last model recorded in the session header, falling back to the configured default only when there is no record. The model name participates in self-description and capability judgment, so a random default makes the model believe it is something else. Log the value as a fallback when it is used.

Related Terms

agents.resume
agents.resume is the DeepSeek Harness API that restores an agent from a persisted session. It accepts resumeSessionId and an optional agentOptions; omitting agentOptions does not fill in a model or provider.— https://github.com/deepseek-ai/deepseek-harness/discussions/10208
agentOptions
agentOptions is the configuration set passed when creating or resuming an agent, including model and provider. It becomes the agent's live options and is the source of prompt variable bindings.— https://github.com/deepseek-ai/deepseek-harness/discussions/10208
system prompt variable
A system prompt variable is a placeholder such as {{model}} or {{provider}} that DeepSeek Harness replaces from runtime context while assembling the prompt. Strict interpolation throws when a placeholder has no value rather than leaving it blank.— https://github.com/deepseek-ai/deepseek-harness/discussions/10208
turn/end
turn/end is the event that marks the end of a conversation turn and carries reason.kind. A kind of error means the turn failed during assembly or the request, not that it completed.— https://github.com/deepseek-ai/deepseek-harness/discussions/10208

Sources