DeepSeek Harness agents.resume missing {{model}}: how to fix
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:
- 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.
- Resume it in a script with
agents.resume({ resumeSessionId })and noagentOptions. Expected: the first message fails with{{model}}has no value. - Read the
turn/endevent. Expected:reason.kindiserror, notabortedorcompleted. - 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:
- The agent loop registers the
modelvariable with a getter that readscontext.agent?.options.model. - The
providervariable reads from the same options object. - A normal agent creation fills both fields through
agentOptions. agents.resumeallowsagentOptionsto be omitted, and omitting it leaves no model on the agent.- 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:
- The UI restore path carries defaults, so the agent's options are populated before the first request.
- Selecting a model in the UI writes it into the agent's live options.
agents.resumedocumentsagentOptionsas optional and performs no default fill.- 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:
- Send a message in any session and confirm it is persisted to disk. Expected: it is still listed after a restart.
- Quit DSH completely and start a clean process. Expected: no agent instances survive.
- Call
agents.resume({ resumeSessionId })withoutagentOptions. Expected: the call itself returns successfully. - Send a message to the resumed agent or run one turn. Expected: it fails immediately with
{{model}}has no value. - Repeat with
agentOptionssupplied. 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.
- Add
agentOptionsto the resume call with at leastmodel, andproviderwhen the route needs it. - Make sure the model name matches the endpoint's capabilities; do not use a placeholder string.
- Run one short turn to verify. Expected: no missing-variable error, and the reply matches the selected model.
- Wrap the call in a helper function so every resume site uses it. Expected: new entry points cannot forget the field.
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.
- Read the session metadata and find the last model or provider selection event. Expected: a model name that matches what was in use.
- Build
agentOptionsfrom those values and callagents.resume. Expected: the resumed agent matches the pre-interruption state. - 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.
- 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.
- Give the
modelgetter a candidate order: live options, then the last session selection, then the configured default. Expected: it throws only when all three are empty. - Use the same order for
provider. Expected: no half-filled state where the model resolves but the provider does not. - 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.
- Add tests for model only, provider only, and neither. Expected: the first two proceed, the third returns a readable error.
- 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:
- Check whether
agentOptions.modelis empty at the resume entry. Expected: a missing model is rejected immediately. - Write an error that says resuming needs a model or
agentOptions, not just a template variable. Expected: the caller can act on it. - Keep the automatic restore from Path 2 as the default and enable strict mode only on request. Expected: both kinds of caller are served.
- 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.
- Filling in only
modelmay surface the next error.providercomes from the same options object, so handle both. - Do not probe the model by sending a trial message after resume. A wrong model still costs tokens and context.
- 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.
- 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.
FAQ
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.
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.
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.
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
- #10208 — agents.resume without agentOptions fails every turn with prompt variable {{model}} has no value· deepseek-ai (GitHub Discussions)
- DeepSeek Harness official repository· deepseek-ai (GitHub)