TL;DR — DeepSeek Harness is less a chatbot and more a programmable agent runtime: model routing, tool calls, evals, and traces in one CLI. In seven days you can go from a greenfield install to a versioned, evaluated, tool-using pipeline that runs reliably against the DeepSeek API. The trick is treating it like a build system — pin models, version prompts, assert on outputs — rather than a playground.
What DeepSeek Harness actually is
DeepSeek Harness is the official command-line harness DeepSeek ships alongside its hosted models. It bundles four things that working engineers tend to glue together by hand:
- A chat and agent CLI that talks to DeepSeek-V3, DeepSeek-Coder, and the R1 reasoning family.
- A tool-use runtime for calling functions, shelling out, and reading files under a sandbox.
- An evaluation harness that scores model outputs against fixtures using deterministic and LLM-judge metrics.
- A trace and replay layer that records every request, response, tool call, and token, so failures are debuggable days later.
It is, in effect, the missing middle layer between the DeepSeek API and something like a LangGraph orchestrator. If you have ever built a “small eval framework” on a Friday afternoon and regretted it on Monday, Harness is the bet you should have taken instead.
Think of Harness as
pytestfor prompts: same discipline, same fixtures, same assertion mental model — just for model behavior.
Day 1 — Install, authenticate, and run your first chat
Start by checking the runtime prerequisites. Harness is a Python package that ships a dsh binary on PATH after install. It expects Python 3.10+ and a reachable API endpoint.
python -m venv .venv
source .venv/bin/activate
pip install --upgrade deepseek-harness
dsh --version
Authentication is a single environment variable. The CLI reads DEEPSEEK_API_KEY and nothing else — no OAuth dance, no device flow.
export DEEPSEEK_API_KEY="sk-..."
dsh models list
You should see the catalog the endpoint exposes: deepseek-chat, deepseek-reasoner, and the coder variants. Pin to one now and never let your scripts float:
dsh chat --model deepseek-chat "Explain backpressure in Kafka in three sentences."
A working response is the entire goal of Day 1. Resist the urge to optimize.
Day 2 — Prompts as files, not strings
The single biggest upgrade in Harness quality of life is that prompts are loaded from disk, versioned in git, and referenced by path. Stop pasting prompts into Python f-strings.
# prompts/summarize_meeting/system.j2
You are a meeting summarizer for an engineering team.
Produce three sections:
1. Decisions (bullets)
2. Action items (owner, due date, bullet)
3. Open questions (bullets)
Do not invent names. If a speaker is unclear, write "Unknown".
Transcript:
{{ transcript }}
The .j2 extension means Jinja templates; .md files are treated as plain prompts. Wire it up:
# harness.yaml
project: meeting-summarizer
default_model: deepseek-chat
prompts:
summarize:
system: prompts/summarize_meeting/system.j2
user: prompts/summarize_meeting/user.md
datasets:
sample:
path: data/sample_transcripts.jsonl
fields: [transcript]
Run the prompt across a tiny dataset:
dsh eval run --config harness.yaml --prompt summarize --dataset sample
By the end of Day 2, you should have one prompt that produces structured output, evaluated against a fixture, with a recorded trace. That is a deployable unit.
Day 3 — Wire up tools
Real work means real I/O. Harness treats tools as plain Python callables decorated with @tool. The runtime enforces argument schemas, sandboxes file paths, and records every invocation in the trace.
# tools/lookup.py
from deepseek_harness import tool
@tool(name="jira.search", description="Search Jira issues by JQL")
def jira_search(jql: str, limit: int = 10) -> list[dict]:
# Auth pulled from env in production; stubbed here.
import os, requests
r = requests.get(
"https://your-domain.atlassian.net/rest/api/3/search",
params={"jql": jql, "maxResults": limit},
headers={"Authorization": f"Bearer {os.environ['JIRA_TOKEN']}"},
timeout=10,
)
r.raise_for_status()
return r.json()["issues"]
Register the tool in your config:
tools:
- tools/lookup.py:jira.search
- tools/lookup.py:gh_open_prs
Run it interactively to watch the loop:
dsh agent --model deepseek-reasoner \
--tools jira.search \
--system prompt tools/researcher.md \
"Which P0 bugs are open in the payments squad this week?"
What you are looking for in the trace is the tool plan, then the tool call, then the final answer. If the model hallucinates a tool name, your schema is too permissive. Tighten argument descriptions before you tighten anything else.
Patterns in production: tools that survive
After running this on three internal projects, the tool surface that holds up is small and boring:
- Read-only search tools (
jira.search,gh.search,confluence.search) — these compose cleanly because they have no side effects. - Typed write tools with explicit
dry_run: boolarguments — even if the model gets it wrong, you can audit before it lands. - Time and id tools (
now,parse_iso,uuid_for) — boring, but eliminates an entire class of “the model made up a date” bugs.
Resist the urge to expose every internal API. Every tool you add is a contract you have to maintain in the eval suite.
Day 4 — Evaluations that mean something
The Day 4 trap is letting “looks right to me” become your eval. Harness supports three metric families that, combined, catch most regressions:
| Metric | What it measures | When to use |
|---|---|---|
exact_match | String equality after normalization | IDs, enums, structured fields |
json_schema | Output conforms to a Pydantic/JSON schema | Tool arguments, extraction |
llm_judge | A second model grades the response on a rubric | Open-ended quality, tone, completeness |
A pragmatic eval config mixes all three:
# evals/extraction.yaml
prompt: prompts/extract_invoice
dataset: data/invoices.jsonl
metrics:
- name: schema_ok
type: json_schema
schema: schemas/invoice.json
- name: total_correct
type: exact_match
field: total
tolerance: 0.01
- name: fields_complete
type: llm_judge
rubric: |
Score 1 if vendor, date, line items, and currency are present and consistent.
Score 0 otherwise.
judge_model: deepseek-chat
Run it and read the failure cases, not the aggregate score:
dsh eval run --config evals/extraction.yaml --report reports/invoices.html
The HTML report cross-links each failing example to its full trace, so you can see exactly which tool call or reasoning step went sideways. This is the single feature that will save you the most hours over the next quarter.
A model score without per-example traces is a number you cannot act on. Always inspect at least the bottom decile.
Day 5 — Trace, replay, and the cost model
By Day 5 you should be paying attention to three numbers in the trace summary:
dsh trace summary --last 7d
You will see a table roughly like:
model calls in_tokens out_tokens p95_latency_s cost_usd
deepseek-chat 412 1.2M 380k 2.4 0.41
deepseek-reasoner 58 690k 210k 14.1 1.87
Two habits to build now:
- Replay before you change anything.
dsh trace replay <trace_id>reruns a captured conversation with a new prompt or model against the same inputs. This is your regression test for prompt edits — far better than guessing whether “the new wording helped.” - Set a per-call budget. Add
max_cost_usdto your agent config. A runaway agent loop against DeepSeek-R1 will burn real money in minutes; the budget guard turns that into a clean error.
agent:
model: deepseek-reasoner
max_steps: 12
max_cost_usd: 0.25
on_budget_exceeded: return_partial
Day 6 — Architecture: where Harness fits in a real system
Harness is the dev-time and CI-time tool. In production, you usually lift the same prompts and tool definitions into a thin service. A common shape:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Web / Slack / │───▶│ Your service │───▶│ DeepSeek API │
│ API consumers │ │ (FastAPI, etc.) │ │ (chat, reason) │
└──────────────────┘ └────────┬─────────┘ └──────────────────┘
│ traces
▼
┌──────────────────┐
│ Harness trace │
│ store (S3 / GCS)│
└──────────────────┘
The service is intentionally thin: it loads harness.yaml, exposes one endpoint per agent, and forwards traces to durable storage. All prompt iteration, eval work, and tool changes still happen in the Harness project — never in the service repo. This keeps the production path boring and the experimentation path fast.
A second pattern worth copying: dual-track evals. Run a small, fast eval (deepseek-chat, ~50 examples) on every commit as a CI gate, and a larger, slower eval (reasoning model, ~1000 examples) nightly. The fast one keeps you honest; the slow one catches drift.
Day 7 — Habits that compound
By Day 7 you have a working loop. The differentiator for the next month is whether you treat it like a build system. Four habits that pay off:
- Pin everything. Model name, prompt SHA, tool version. Trace payloads include hashes for a reason — use them.
- Treat prompts as code. They live in git, they have review owners, they have changelogs. “I tweaked the prompt” is not a commit message.
- Keep eval sets public inside the team. A private leaderboard that nobody reads is a tax with no return.
- Separate “make it work” from “make it fast.” Day 1–4 is correctness against fixtures. Day 5 onward is latency, cost, and reliability against production traces.
The teams that win with LLM tooling are not the ones with clever prompts. They are the ones whose eval suite tells them, on a Tuesday morning, that the new model quietly regressed extraction accuracy by 4%.
Key Takeaways
- DeepSeek Harness is a programmable agent runtime — chat, tools, evals, and traces — not a chatbot wrapper.
- Treat prompts as files in git and evals as fixtures; this is the single biggest quality-of-life upgrade.
- Wire tools through typed Python callables with
@tooland keep the surface small, read-only, and explicit. - Mix
exact_match,json_schema, andllm_judgemetrics, and always inspect failing traces, not just aggregate scores. - Replay captured traces before shipping prompt changes, and set a per-call cost budget to defang runaway agents.
- In production, keep Harness as the dev/CI tool and expose a thin service that forwards traces to durable storage.
- Pin model, prompt, and tool versions, and run a fast eval on every commit plus a slow eval nightly to catch drift.