Harness for DeepSeek V4-Pro / V4-Flash. Python lib (pip install deepseek-harness) + dsh CLI + MCP server (npx @deepseek-harness/mcp) + Anthropic SKILL.md. 16 documented protocol quirks, 12 probes, 270+ trials.
SaferSkills independently audited deepseek-harness (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
When you call DeepSeek V4-Pro or V4-Flash via the OpenAI-compatible API, you MUST follow the 10 contract rules below. Violating any one of them causes a documented production bug (the upstream issue and our reproduction probe are cited per rule).
deepseek-v4-pro defaults to thinking=enabled. Every call then burns 30-300 reasoning_tokens even on trivial prompts.
client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
extra_body={"thinking": {"type": "disabled"}}, # ← saves money on every non-reasoning call
)For TypeScript / openai-node, put thinking at the top level of the request, not inside extra_body (the JS SDK passes unknown top-level keys through):
await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages,
thinking: { type: "disabled" },
} as any);reasoning_content on assistant messagesIf thinking IS enabled and you re-send a prior assistant message that has tool_calls, you must include the original reasoning_content field. Otherwise the next request returns:
HTTP 400: The reasoning_content in the thinking mode must be passed back to the API.(Reproduced in reports/probes/probe_2_reasoning_lifecycle.py 3/3 trials on V4-Pro and V4-Flash, 2026-05-09.)
msg = response.choices[0].message
history.append({
"role": "assistant",
"content": msg.content,
"tool_calls": _serialize_tool_calls(msg.tool_calls),
"reasoning_content": getattr(msg, "reasoning_content", None), # ← REQUIRED
})When a NEW user turn arrives, you MAY strip reasoning_content from prior assistant messages — DeepSeek doesn't require it across user-turn boundaries, and keeping it bloats the prefix-cache key.
max_tokens (default 4096)Without an output cap, reasoning_content can stream 8000+ chunks (probes/probe_9_reasoning_runaway.py measured 26 KB / 84 s on a self-doubt prompt) and downstream Electron clients (ChatWise, Cherry Studio) crash with RangeError: Invalid string length once their string buffer hits V8's 512 MB ceiling.
tool_calls by tc.index, not list orderDeepSeek interleaves chunks across parallel tool calls (probe_7 100% interleave on 3/3 V4-Pro and V4-Flash trials). Use a dict[int, slot]:
tool_call_acc: dict[int, dict] = {}
for chunk in stream:
for tc in (chunk.choices[0].delta.tool_calls or []):
slot = tool_call_acc.setdefault(tc.index, {"id": None, "name": None, "arguments": ""})
if tc.id: slot["id"] = tc.id
if tc.function and tc.function.name: slot["name"] = tc.function.name
if tc.function and tc.function.arguments: slot["arguments"] += tc.function.arguments"".join, NOT state += chunkDeepSeek streams 1-3 chars per reasoning chunk. state.text += chunk is O(n²) string allocation:
buf = []
for chunk in stream:
if c := (chunk.choices[0].delta.content or ""):
buf.append(c)
final = "".join(buf)DeepSeek emits ~3 chunks per response with choices == []. Check truthiness before indexing:
for chunk in stream:
choices = chunk.choices or []
if not choices:
if chunk.usage is not None: usage = chunk.usage
continue
...The V4-Pro / V4-Flash hard ceiling is exactly 2^20 = 1,048,576 tokens (probe_6b validated). The server enforces len(messages_tokens) + max_tokens <= 1,048,576 and returns 400 if exceeded.
DeepSeek's prefix cache buckets in 256-token blocks and gives a 50× discount on hits. To maximise:
prompt_cache_hit_tokens (DeepSeek-native) AND prompt_tokens_details.cached_tokens (OpenAI-shape) — both are returned/beta endpoint when calling V4 with tools/beta silently remaps deepseek-v4-pro → legacy deepseek-reasoner, which rejects specific tool_choice={"type":"function","function":{"name":"..."}}. Use https://api.deepseek.com for tool-using flows.
strict: true is empirically OK on V4 (despite #1069 still being open)You MAY enable function.strict=true and additionalProperties=false on V4-Pro / V4-Flash — the historic JSON-corruption bug (deepseek-ai/DeepSeek-V3#1069) was not reproducible in 32 trials on 2026-05-09. Still validate JSON post-hoc with jsonschema.
If the user's environment allows installing third-party libraries, use any of:
| form | install | command |
|---|---|---|
| Python lib | pip install deepseek-harness | from deepseek_harness import DeepSeekHarness |
| Python CLI | pip install deepseek-harness-cli | dsh chat, dsh doctor, dsh validate |
| MCP server | npx -y @deepseek-harness/mcp | wire into Claude/Cursor/Cline/ChatWise MCP config |
If they want zero deps beyond openai, copy scripts/safe_init.py (in this skill folder) into their project. It implements all 10 rules in 200 lines.
Every claim in this skill is backed by a runnable probe in reports/probes/:
# Reproduce the reasoning_content 400:
python reports/probes/probe_2_reasoning_lifecycle.py --n 3
# Expected: 3/3 phase-B BadRequestError with the verbatim error message above.
# Confirm tool-call leakage rate at 0% on official endpoint:
python reports/probes/probe_3_tool_call_leakage.py --n 30
# Map cache prefix sensitivity:
python reports/probes/probe_5_cache_prefix_sensitivity.py --n 8See reports/REPORT_2026-05-09.md for the full 16-finding report.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.