AI

DeepSeek Harness With Ollama: Local Model Setup, vLLM, llama.cpp

Pointing DeepSeek Harness at Ollama should take one command, and on paper it does. The ollama launch dsh command installs the harness, writes a config and opens the Web UI, but on a current install that Web UI comes up with a “Get started” dialog asking for a DeepSeek key and a model picker that says “No models available”, because the overlay Ollama writes targets a settings file dsh stopped reading in September. You can still run DeepSeek Harness on a local model; the route just has to go where dsh looks.

Original content from computingforgeeks.com - post 170966

This guide covers that fix first, then the full local route: the patch file that carries an Ollama, vLLM or llama.cpp provider, the No API key for provider: ollama error, the context window trap, the compat switches and the Web UI forms. Every Ollama command below was run on Ubuntu 26.04 with dsh 0.2.1-rc.2 and Ollama 0.40.9 in October 2026; the vLLM, llama.cpp and GPU timing sections come from our August run on an RTX 4090 and say so where they start.

Why ollama launch dsh shows no models

ollama launch dsh starts the Web UI with one extra overlay, and that overlay asks a plugin to read ~/.ollama/launch/dsh/settings.yaml. dsh 0.1.7 removed the file-backed settings provider that honoured that path, so the overlay is composed but its path setting silently does nothing. With no Ollama route loaded, dsh falls back to its bundled DeepSeek route, and on a box without a DeepSeek key you get exactly this:

DeepSeek Harness Web UI after ollama launch dsh showing the Get started dialog and Select model with no Ollama models

You can see what the launcher actually ran from the process table while the Web UI is up. It boots the web profile with a --patch overlay and exports a placeholder key, which is the part worth remembering for the fix.

ps -eo args | grep "[d]sh web"
tr '\0' '\n' < /proc/$(pgrep -f "bin/dsh web" | head -1)/environ | grep OLLAMA_LAUNCH
cat ~/.ollama/launch/dsh/ollama.cordis.yml

You’ll see the dsh process, the placeholder key in its environment, and an overlay of two lines pointing at a path. Our lab ran as root, so the paths read /root/.ollama; as a normal user they sit under your home directory.

node /usr/local/bin/dsh web --patch /root/.ollama/launch/dsh/ollama.cordis.yml --no-open
OLLAMA_LAUNCH_DSH_API_KEY=ollama
- config:
    path: /root/.ollama/launch/dsh/settings.yaml
  id: settings

In this release the settings plugin has no path field at all; it only imports a legacy settings.yaml from the dsh home directory, once. The break was reported to Ollama as issue 18699, which was closed as not planned, and it still reproduces with the Ollama build current in October 2026. The headless profile fails the same way, only louder: MISSING_CREDENTIAL on the deepseek-official route.

We fixed it by declaring the route ourselves in ~/.dsh/cordis.patch.yml, the home-level patch every profile reads. Point your route’s apiKeyEnv at OLLAMA_LAUNCH_DSH_API_KEY and ollama launch dsh starts working too, because the launcher already exports that variable. The diagram shows where each layer sits:

Architecture diagram of where dsh 0.2 reads a local model route: ollama launch dsh overlay path ignored, home cordis.patch.yml carries the Ollama, vLLM and llama-server routes

Profile patches still exist (~/.dsh/profiles/web/cordis.patch.yml is where the Web UI saves its own edits), but a route written there only reaches that one profile. The home patch is applied after them, so it is the one place a local route reaches dsh web, dsh headless and the launcher alike.

Prerequisites

You need a Linux host with a current Node, an inference server the harness can reach over HTTP, and dsh itself. If dsh is new to you, getting started with dsh covers the first run and the Web UI. The lab for this refresh was:

  • Ubuntu 26.04.1 LTS in a rootless container, 8 vCPU, no GPU, no systemd
  • Node 22.22.1 from the Ubuntu 26.04 archive
  • dsh 0.2.1-rc.2 (npm latest) and Ollama 0.40.9
  • qwen3:1.7b as the local model

Ubuntu 26.04’s own Node runs the current harness as is. Ubuntu 24.04 still ships Node 18.19.1, which failed at launch in our August test, so on 24.04 take Node 22 or 24 from NodeSource before installing anything. Sizing is driven by the model rather than the harness: weights plus KV cache have to fit in memory, and an agent pushes several thousand tokens of tool schemas through the model on every step. An 8B model at Q4 needs about 5 GB of VRAM for weights; the CPU-only box here worked, slowly, and the timing section shows how slowly.

Install Node and npm from the archive along with two small tools used below to read JSON and the compressed session logs. On Ubuntu the nodejs package does not pull in npm, and ollama launch dsh needs both:

sudo apt update
sudo apt install -y nodejs npm jq zstd
node -v

On 26.04 node -v should print v22.22.1 or newer. On 24.04 it prints an 18.x release, which is your cue to switch to NodeSource first.

Install Ollama and pull a model for DeepSeek Harness

dsh only ever talks to Ollama’s OpenAI-compatible endpoint at /v1, so a stock Ollama install is all it needs. The Ollama install steps for Ubuntu cover the service in detail; the short version is the official script:

curl -fsSL https://ollama.com/install.sh | sh
ollama -v

Current releases of the script need zstd because the Linux bundle ships as .tar.zst, which is why it went in with jq above. Pull a model next. On a CPU-only box stay small; on a 24 GB card qwen3:8b is a sensible start:

ollama pull qwen3:1.7b
curl -s http://127.0.0.1:11434/v1/models | jq -r '.data[].id'

Whatever that last command prints is the model id dsh needs, character for character. Ollama tags carry a colon, so the id is qwen3:1.7b, not qwen3:

qwen3:1.7b

For model management beyond a single pull, the Ollama commands cheat sheet lists the pull, list, stop and copy commands this guide leans on.

Match the context window to what Ollama serves

Do this before the first agent run, because it is the trap that costs the most and shows the least. The contextWindow you declare in the patch tells dsh how much room it has, and nothing asks the server, so the number is taken as fact. Ollama picks its own default from available VRAM, and the documented tiers are coarse: 4k below 24 GiB, 32k from 24 to 48 GiB and 256k above that. A 12 GB or 16 GB card therefore serves 4,096 tokens while the harness builds prompts several times that size.

Set it explicitly. For the packaged service, put the value in a drop-in so it survives restarts:

sudo systemctl edit ollama.service

Add the environment line under a service section, using the same number you’ll declare as contextWindow in the next section:

[Service]
Environment="OLLAMA_CONTEXT_LENGTH=32768"

Restart, load the model once, and read the server’s own answer rather than trusting the config.

sudo systemctl daemon-reload
sudo systemctl restart ollama
ollama run qwen3:1.7b 'hi' >/dev/null
ollama ps

The CONTEXT column is the server’s real limit, and the patch has to match it. Our lab had no systemd (it ran in a container), so we started the server with OLLAMA_CONTEXT_LENGTH=32768 in the environment instead, and it reported exactly that.

NAME          ID              SIZE      PROCESSOR    CONTEXT    RUNNER      UNTIL
qwen3:1.7b    8f68893c685c    5.3 GB    100% CPU     32768      llamacpp    4 minutes from now

Skip this on a CPU box and you lose more than you’d guess. With no GPU our lab server logged default_num_ctx=4096 as its VRAM-based default at startup, while the first request dsh sends is close to 4,900 tokens of system prompt, tool schemas and your message. That request has no older turns to drop, so for most library models, qwen3 included, Ollama cuts tokens out of the prompt itself: it keeps the first few and the last 2,000 or so, discarding more than half of the system prompt and tool definitions. It logs truncating input prompt at the default level when it does that, and a DeepSeek-architecture model, which can’t shift context, rejects the request instead. Later in a long session Ollama also drops whole old turns to make the history fit, and that step is only logged at debug level, so add Environment="OLLAMA_DEBUG=1" to the same drop-in when you suspect it, then restart and search the journal:

sudo systemctl restart ollama
journalctl -u ollama | grep -E "exceed context length|truncating input prompt"

Any hit means the agent has been working from a clipped prompt or conversation. vLLM behaves better here because --max-model-len is explicit and the engine refuses to start when the KV cache can’t cover it.

Add the Ollama route to cordis.patch.yml

Install dsh globally with sudo before you ever run ollama launch dsh as a normal user, because the launcher’s own npm install -g runs without sudo and can’t write to the apt npm prefix. Then create the home patch. It’s a YAML list of entries, each targeting a plugin by its id, which is a different shape from the flat settings.yaml older guides (including the first version of this one) show:

sudo npm install -g @deepseek-ai/dsh@latest
dsh --version
mkdir -p ~/.dsh
vim ~/.dsh/cordis.patch.yml

Add the provider route and make it the default model for new sessions. This is the exact file the lab ran:

- id: llm-pi-ai
  config:
    providers:
      ollama:
        displayName: Ollama
        apiKeyEnv: OLLAMA_API_KEY
        api: openai-completions
        baseURL: http://127.0.0.1:11434/v1
        models:
          - id: qwen3:1.7b
            name: Qwen3 1.7B
            contextWindow: 32768
            maxTokens: 8192
- id: agent-default-model
  config:
    provider: ollama
    model: qwen3:1.7b

dsh refuses a hand-declared route where it’s written unless it has the first three of these, and the fourth is what keeps it from failing at request time:

  • api, the wire protocol (openai-completions for Ollama, vLLM and llama-server)
  • baseURL, which for these servers ends in /v1
  • a non-empty models list
  • an apiKeyEnv reference (or an Authorization header), even for a server that checks no key

A route without the last item loads fine and then fails on its first request, as the next section shows. The provider key (ollama) is the id requests select, and it is permanent once sessions reference it, so pick it with that in mind. If you launch through Ollama rather than calling dsh yourself, change apiKeyEnv to OLLAMA_LAUNCH_DSH_API_KEY; the next section explains why the key line is not optional.

Check that the patch composed before you run anything, because the dump is the only view of what dsh will really load:

dsh headless --dump-config | grep -A8 "id: llm-pi-ai"

A route that made it in shows its provider block under the entry. A bare name: line with nothing under it means dsh never saw your file, usually because it sits under a profile directory instead of directly in ~/.dsh.

Fix “No API key for provider: ollama”

A local Ollama server needs no API key, which makes apiKeyEnv look like a field you can leave out. Leave it out and the first run dies before a request leaves the box.

Error: “PI_AI_ERROR: No API key for provider: ollama”

A route that names no credential is treated as configured but keyless, and the OpenAI-compatible client underneath still insists on a bearer token, so dsh exits 1 with this in headless mode:

Terminal showing dsh 0.2.1 headless failing with PI_AI_ERROR No API key for provider ollama on a keyless Ollama route

What dsh wants is a reference in the route plus any non-empty value in the environment, because nothing on the Ollama side checks it. Keep the name in apiKeyEnv and the exported name identical:

export OLLAMA_API_KEY=ollama

The other half of this mistake is an apiKeyEnv that names a variable the process never got. In the Web UI that does not print an error at all: a credential that cannot be resolved gives the route an empty model catalog, so you get the “Get started” dialog and “No models available”, the same picture as the broken launcher above. We hit exactly that by starting dsh web from a shell without the export, which is why a missing export and the ollama launch dsh bug look identical in the browser. For a box you keep, put the export in the shell profile or unit file that starts dsh.

Error: “MISSING_CREDENTIAL: llm-deepseek: no API key for provider route deepseek-official”

You get this one when your local route never loaded and dsh fell back to its bundled DeepSeek default. Two things cause it. The first is ollama launch dsh without a home patch, covered above. The second catches anyone upgrading from an early release with a ~/.dsh/settings.yaml: the first profile you boot imports that file into its own profile patch, renames it to settings.yaml.imported, and every other profile is left with nothing. We booted dsh web once against an old-style file, then ran headless on the same home:

dsh: MISSING_CREDENTIAL: llm-deepseek: no API key for provider route "deepseek-official"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment

We found the route sitting in ~/.dsh/profiles/web/cordis.patch.yml, invisible to headless. Move the llm-pi-ai and agent-default-model entries from that file into ~/.dsh/cordis.patch.yml and both profiles see them.

Run the first task on the local model

Export the placeholder key the route references first (export OLLAMA_API_KEY=ollama, explained in the previous section). Headless answers one task and exits, which makes it the fastest proof that the route works end to end. Give it a file worth reading.

mkdir -p ~/dsh-work
cd ~/dsh-work
printf 'name,qty\nwidget,3\nbolt,12\n' > parts.csv
time dsh headless "Read parts.csv and tell me the total quantity."

Headless now streams the model’s reasoning to the terminal as dsh: reasoning: blocks before the answer. On the 1.7B model that reasoning was long and not always right (its first read asked for part.csv, a glob found nothing, and the third step read the real file), but the final answer was correct.

The total quantity is 15.

**Step-by-Step Explanation:**
1. The CSV file contains headers ("name,qty") and data rows.
2. The data rows with actual values are:
   - "widget,3" → quantity 3
   - "bolt,12" → quantity 12
3. Summing the quantities: 3 + 12 = **15**

Thus, the final answer is **15**.

real	9m28.541s
user	0m7.691s
sys	0m1.012s

Nine and a half minutes for a three-line CSV is the CPU tax, and it’s worth reading where it went. The first step alone took about 380 seconds (Ollama logged 6m20s for that request), because Ollama processed a 4,859-token prompt at about 61 to 84 tokens per second and then generated at under 3 tokens per second on this 8 vCPU box. The three steps after it reused the cached prefix and finished in about 32, 54 and 90 seconds. A GPU removes most of that, which is what the timing table further down shows.

Every run leaves a compressed append-only log under ~/.dsh/sessions/, and the file name changed with the 0.2 line to session.v4.jsonl.zstd. The request/context event records which route the session really used:

LATEST=$(ls -t ~/.dsh/sessions/*/session-*/session*.jsonl.zstd | head -1)
zstd -dc "$LATEST" | jq -c 'select(.type=="request/context").data'

Provider, model and the context window the harness believed it had, which settles any doubt about whether a session ran locally.

{"provider":"ollama","model":"qwen3:1.7b","contextWindow":32768}

We glob session* rather than the old exact name so the same command reads logs written before and after the rename.

Make ollama launch dsh work

With the home patch in place, the launcher works as a convenience again. Change one line in ~/.dsh/cordis.patch.yml so the route reads the key the launcher exports:

        apiKeyEnv: OLLAMA_LAUNCH_DSH_API_KEY

Then launch as Ollama’s documentation shows. Anything after -- goes to dsh itself, so --no-open keeps it from trying to start a browser on a headless server.

ollama launch dsh --model qwen3:1.7b -y -- --no-open

You’ll get the tokenised Web UI address back, and the composer now opens on the Ollama model with no sign-in dialog, and the picker groups it under Ollama:

dsh web: http://127.0.0.1:3080/?token=REDACTED-SESSION-TOKEN

Opening that link shows the route the home patch declared:

DeepSeek Harness model picker listing the local Ollama Qwen3 1.7B model under the Ollama group

Two things the launcher still gets wrong are worth knowing even with the fix. It writes contextWindow: 40960 for qwen3:1.7b into its own (ignored) file, while the server in this lab served 32,768, and its model list mixes local tags with Ollama Cloud models that need ollama signin. Your home patch is what dsh reads, so the numbers in it are the ones that count. With this key name, plain dsh headless runs need export OLLAMA_LAUNCH_DSH_API_KEY=ollama in your shell profile too, or they fail with MISSING_CREDENTIAL. If you never use the launcher, keep OLLAMA_API_KEY and skip this section.

Set the compat switches

dsh shapes each request from the provider id and base URL, and an address it does not recognise is addressed as though it were OpenAI itself. Most local servers accept that, but stricter gateways refuse the developer role or the max_completion_tokens field. We captured the wire with a logging proxy in August, same task and same box, three configurations:

Request fieldPlain routereasoningEfforts plus reasoning: highBoth, with compat set
System prompt rolesystemdevelopersystem
Output cap fieldmax_completion_tokensmax_completion_tokensmax_tokens
Thinking controlabsentreasoning_effort: highreasoning_effort: high plus thinking: {type: "enabled"}
OpenAI-only extrasstore: falsestore: falsestore: false
Tool schemas sent252525

The developer role appears the moment a model declares selectable thinking levels, whether or not an effort is being sent, and store goes out on every request. The switches live under compat, per route or per model, and the 0.2 provider guide still documents the same three. In patch form, the Ollama route with all of them set looks like this; it’s the exact file that composed cleanly in the October lab:

- id: llm-pi-ai
  config:
    providers:
      ollama:
        displayName: Ollama
        apiKeyEnv: OLLAMA_API_KEY
        api: openai-completions
        baseURL: http://127.0.0.1:11434/v1
        reasoning: high
        compat:
          supportsDeveloperRole: false
          maxTokensField: max_tokens
          thinkingFormat: deepseek
        models:
          - id: qwen3:1.7b
            name: Qwen3 1.7B
            contextWindow: 32768
            maxTokens: 8192
            reasoningEfforts:
              off:
              high: high
      vllm:
        displayName: vLLM
        apiKeyEnv: VLLM_API_KEY
        api: openai-completions
        baseURL: http://127.0.0.1:8000/v1
        compat:
          supportsDeveloperRole: false
          maxTokensField: max_tokens
        models:
          - id: Qwen/Qwen3-4B-Instruct-2507
            name: Qwen3 4B Instruct
            contextWindow: 32768
            maxTokens: 8192
- id: agent-default-model
  config:
    provider: vllm
    model: Qwen/Qwen3-4B-Instruct-2507

supportsDeveloperRole: false sends the system prompt as a system message, maxTokensField: max_tokens restores the output-cap field every OpenAI-compatible server understands, and thinkingFormat: deepseek switches the thinking control to the DeepSeek dialect. Under reasoningEfforts, each key is the level the picker offers and the value is the spelling sent on the wire; a bare off: sends nothing, except under the DeepSeek dialect, where it sends thinking: {type: "disabled"}. Give every compat switch you name a value, because a key left empty after the colon is refused rather than ignored.

In August, current Ollama and vLLM both accepted the unfixed shape and finished the task anyway. Treat the switches as insurance against a stricter gateway and as the only way to control the thinking dialect, not as something a local Ollama demands.

Serve the same agent from vLLM

vLLM enforces things Ollama waves through, so it needs flags for agent work and not just a model. The serve command and the errors below come from the August GPU run on vLLM 0.27.1; the vLLM route in the patch above is the October file, which reached the transport stage against an absent server (dsh: TRANSPORT: Connection error.), proving dsh accepted it:

vllm serve Qwen/Qwen3-4B-Instruct-2507 \
  --host 127.0.0.1 --port 8000 \
  --max-model-len 32768 \
  --gpu-memory-utilization 0.85 \
  --enable-auto-tool-choice --tool-call-parser hermes

Export a placeholder key for the route (export VLLM_API_KEY=vllm) for the same reason as with Ollama. Both routes can live in the same patch and the picker lists both. Two failures are to know before you hit them.

Error: ‘”auto” tool choice requires –enable-auto-tool-choice and –tool-call-parser to be set’

Leave those two flags off the server and every agent turn fails. The harness never names a tool choice; vLLM defaults tool_choice to auto whenever a request carries tools, then rejects its own default with a 400 that dsh passes through verbatim:

dsh: INVALID_REQUEST: 400: {"message":"\"auto\" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set","type":"BadRequestError","param":null,"code":400}

The parser is model-specific, and the wrong one gives you an agent that talks about calling tools without calling any. hermes worked for Qwen3 instruct in our run, although the vLLM tool calling reference still lists it only against the Qwen2.5 family and sends the Qwen3-Coder models to qwen3_xml, so check that table for your own model. The server side is covered in the vLLM production install guide.

Restarting vLLM with both flags is the whole fix; nothing changes on the dsh side.

Error: “4.5 GiB KV cache is needed, which is larger than the available KV cache memory”

Running Ollama and vLLM on one GPU causes this. Ollama keeps its model resident after a request, vLLM pre-allocates against what is left, and the engine refuses to start rather than serve a context it can’t honour:

ValueError: To serve at least one request with the model's max seq len (32768), (4.5 GiB KV cache is needed, which is larger than the available KV cache memory (3.57 GiB).

Unload the other model first. Ollama frees the card immediately and reloads on the next request, which took VRAM in use from 9,986 MiB to 1 MiB on the August box.

ollama stop qwen3:8b
nvidia-smi --query-gpu=memory.used --format=csv,noheader

With the card to itself, vLLM loaded 7.64 GiB of weights in 5.06 seconds and reported a KV cache of 86,752 tokens. The API took 70 to 100 seconds from launch to answering on /v1/models, so scripts that start the server and fire a task straight away need to poll rather than sleep. If the driver stack is not in place yet, start with the NVIDIA driver and CUDA setup.

Use llama.cpp, but not the CPU build

llama-server speaks the same protocol, so its route is the vLLM block with port 8080 and the model name the server reports. Getting the binary is where a packaging detail costs people time: llama.cpp tags a build per release, and in October 2026 the GitHub releases/latest endpoint returned v0.7.0 while the newest build was b11836. Ask for the first entry in the list instead:

LC_TAG=$(curl -sL "https://api.github.com/repos/ggml-org/llama.cpp/releases?per_page=1" \
  | grep -m1 '"tag_name"' | grep -o 'b[0-9]\+')
echo "$LC_TAG"
curl -sLO "https://github.com/ggml-org/llama.cpp/releases/download/${LC_TAG}/llama-${LC_TAG}-bin-ubuntu-x64.tar.gz"

Each build also ships ubuntu-cuda-12.8-x64, ubuntu-cuda-13.4-x64, ubuntu-vulkan-x64 and ubuntu-rocm-10.0-x64 tarballs, and for agent work you want one of those, swapped into the file name above. Extract, change into the directory and start the server.

tar -xzf "llama-${LC_TAG}-bin-ubuntu-x64.tar.gz"
cd "llama-${LC_TAG}"
./llama-server -hf ggml-org/Qwen3-1.7B-GGUF --host 127.0.0.1 --port 8080 -c 16384 --jinja

--jinja isn’t optional for an agent. Without it, tool calls do not come back in OpenAI shape and the agent becomes a chatbot. With it, the August run returned a proper tool call.

finish_reason: tool_calls
tool_calls: [{"type": "function", "function": {"name": "read_file", "arguments": "{\"path\": \"parts.csv\"}"}, "id": "ECiouDPXhQvue8v5dQOFiQBYRwq2L8Gc"}]

A plain ubuntu-x64 build has no GPU backend. On the share-limited container CPU we used in August it generated 2.23 tokens per second and a single agent turn didn’t finish inside a 300-second timeout. Ollama on the October 8 vCPU box did finish, in 9.5 minutes, which tells you the same thing from the other side: CPU inference is fine for chat and painful for an agent. The llama.cpp guide covers GPU builds.

Pick the model before you pick the server

Our table mixes two labs, so read the rows by date. The GPU rows ran twice each on an RTX 4090 in August; the October rows are the CPU-only box from this refresh.

Server and modelHardwareRun 1Run 2Notes
Ollama, qwen3:8b (Q4, thinking on)RTX 4090, Aug 202652.4 s65.9 sCorrect both runs
vLLM, Qwen3-4B-Instruct-2507 (bf16, no thinking)RTX 4090, Aug 20264.38 s4.38 sCorrect both runs
llama.cpp CPU build, Qwen3-1.7B GGUFContainer CPU, Aug 2026timeoutnot run2.23 tok/s, killed at 300 s
Ollama, qwen3:1.7b (thinking on)8 vCPU, Oct 20269 min 28 snot runCorrect, 4 steps, one wrong file name
Ollama, qwen3:1.7b, Web UI turn8 vCPU, Oct 20267 min 28 snot runCorrect, 2 steps, 48% cache hit

Models differ in size, quantisation and whether they think, so the gap isn’t the serving stack alone, and that’s the real lesson. A thinking model spends most of a turn on reasoning tokens the agent loop then throws away, and for tool-driven work an instruct model of half the size finished an order of magnitude faster with the same answer. Reach for reasoning when the task needs it. The Ollama 8B rows also predate the context step above: that server ran its 32,768-token VRAM default while the route declared 40,960, which is the mismatch that step exists to close.

You can repeat the comparison on your own hardware, because the Web UI puts per-turn numbers next to each answer. This is the October turn through ollama launch dsh with the home patch in place.

DeepSeek Harness Web UI session on local Ollama qwen3 1.7b completed in 7m 28s with 15.8K tokens and 48 percent cache hit

Listing one file took 7 minutes 28 seconds over two steps at 2.8 tokens per second, with 15.8K tokens of usage and a 48% cache hit. In August the 8B model on the RTX 4090 ran a similar two-step task at 61 tokens per second with a 7.1-second time to first token, 27.7 seconds of model time against 0.2 seconds of tool time, and 14.7K input tokens. Same harness and the same shape of task, and the GPU and the bigger, faster model together account for a gap of more than ten times.

Add a local provider from the Web UI

Settings, then Models, is where routes report their state. A hand-declared route carries a Custom badge, and the dot is the quickest health check you have: green means dsh resolved a credential for that route, red means it didn’t, which is what the stock DeepSeek row looks like on a box without a DeepSeek key:

DeepSeek Harness Settings Models page with the Ollama route marked Custom and a green credential dot

You can create a route here instead of typing YAML. “Add model provider” now opens on a Third-party tab for catalog providers; switch to “Custom model API” and it asks for a lowercase Provider ID, a display name, the base URL, the protocol (OpenAI Chat Completions, OpenAI Responses or Anthropic Messages) and a key. “Fetch available models” queries the endpoint and offers what it found, which is the safest way to avoid a mistyped model id:

DeepSeek Harness Custom model API form fetching available models from Ollama and offering qwen3 1.7b

The form is fine for a first route, but you should know where it stops before you rely on it:

  • it writes to ~/.dsh/profiles/web/cordis.patch.yml, so headless never sees the route
  • the key lands in ~/.dsh/.credentials.yaml, not in an environment variable
  • compat switches, headers and timeouts aren’t on the form

That’s why we keep routes in the home patch and use the form only to discover model ids. On a loopback connection the Settings header carries an “Open configuration file” button; reach the UI across the network and that button is gone.

Where the local route still bites

Tool schemas are the hidden cost. Standard mode sent 25 tool schemas on every request in both labs, with no MCP servers attached, and the first headless request in October came to 4,859 input tokens. On a local model you pay that in prompt-processing time rather than dollars, and on CPU it was most of the wait. Trimming the preset is the lever that matters; comparing dsh with Claude Code covers what each preset contains.

Prompt caching changed since the first version of this guide. In August the Ollama session showed a 0% cache hit rate because Ollama didn’t report cached tokens. The October session log shows cacheReadTokens of 4,863 on the second step and 5,519 on the fourth, and the Web UI turn closed on a 48% cache hit, so current Ollama reports the reused prefix and dsh counts it. vLLM still reports it only when the server runs with --enable-prompt-tokens-details, which the serve command above doesn’t set.

One more default needs pinning down. A model with no declared capacity falls back to the route default of 262,144 tokens, which is a guess, so declare contextWindow on every model. Hand-declared models are also text-only until you add input: [text, image]. If you run DeepSeek Harness on Ollama day to day, the setup we’d keep is the home patch above with the launcher-compatible key name exported in your shell profile, a context length pinned on both sides, and an instruct model on a GPU. Anything less works, but you’ll feel it on every turn.

Keep reading

Ollama Models Cheat Sheet: Best Models by VRAM, Tested (PDF) AI Ollama Models Cheat Sheet: Best Models by VRAM, Tested (PDF) Open Source LLM Comparison Table (2026) AI Open Source LLM Comparison Table (2026) OpenCode CLI Cheat Sheet: Commands, Sessions and Fixes (PDF) AI OpenCode CLI Cheat Sheet: Commands, Sessions and Fixes (PDF) Best Laptops for Local LLMs: RTX 50 vs 128GB Unified Memory AI Best Laptops for Local LLMs: RTX 50 vs 128GB Unified Memory Black Friday Mini PC Deals: Proxmox, Local AI and Home Assistant AI Black Friday Mini PC Deals: Proxmox, Local AI and Home Assistant NVFP4 vs FP8 vs BF16 on RTX 5090: TensorRT-LLM Benchmarks AI NVFP4 vs FP8 vs BF16 on RTX 5090: TensorRT-LLM Benchmarks

Leave a Comment

Press ESC to close