DeepSeek Harness · Models & External Integration

Testing a Nondeterministic System

Deterministic replay, property-based testing, and a fault server built to trick LLM clients. Core materials: docs/testing.zh.md and packages/test-support/.

Course goalAgent behavior depends on model output, and model output is different every time — how do you test that system? After this lesson you can name DSH’s three weapons: deterministic replay that turns real session logs into replay scripts, so nondeterminism lives only in the recording; a scriptable fault server that manufactures disconnects, half-packets, and rate limits for LLM clients; and property-based testing that sweeps protocol code with random interleaved sequences — it caught a real bug on its first run.
Interactive demo · Fault-injection & replay lab

Play first, then talk. Scenario A is a real HTTP fault server: behaviors queue up, each request consumes one, watch how the client responds. Scenario B is deterministic replay: take a real session log, derive a replay script in one shot and re-run it, then tamper with one line and watch the diff testify on the spot.

Fault server · behavior script queueseed 0x5A3C
(empty)
Client side · classify / retry / log
(empty)
Pick a scenario and hit Play, or scroll here to auto-play Scenario A.
How it works · Nail the foundation first

There’s only one way to test a nondeterministic system: fence off the uncertain parts and make everything else deterministic. DSH’s test layers (docs/testing.zh.md) are built around that idea. Unit tests hunt edge cases, error paths, event ordering, and concurrency races; CI’s coverage gate demands 100% per file under packages/*/*/src (AGENTS.md line 65, Commands section). The docs also say it flat: line coverage is necessary, never sufficient — unrun lines are usually dead code to delete, not tests to add.

Real API tests with keys are another layer. Here’s a line with real identity:

“We are DeepSeek — don’t skimp on real API tests. Keyless tests only prove the lower pathways; only keyed runs prove the agent can talk to a real model and work.” Source: docs/testing.zh.md, “keyed strategy” section, verified on 2026-08-13

Inference is cheap for us, so smoke tests go real: boot a real example, send a prompt, check the outside world. Assertions matter too: e2e should re-read files and re-run commands to verify results — keyword sniffing on the agent’s own output lets a cheating agent pass. Environments without keys skip automatically and block nobody.

How it works · Logs as test assets

The main act is replay. Last lesson covered this (see LLM Adapter Layer): every streaming chunk lands as an assistant/chunk event in the session log as-is. The dsh-llm-replay plugin flips that around: take a recorded session.jsonl, group chunk events by (turn, step), and each group is the full chunk sequence from one model call back then. In tests the real agent runs as usual — only the model end is swapped for a replay adapter that emits the recorded chunks frame by frame. Nondeterminism lives only in that one recording; every re-run is byte-identical afterward, no API Key needed.

That’s what “logs as test assets” means: fixtures aren’t hand-written mocks — they’re production-format session logs themselves. Snapshot tests pin the whole assembled behavior with them; change one line of code and fork the behavior, and the diff lights up red on the spot. A neat detail sits in fork (forked sessions): a child session’s log starts by inheriting the parent’s seed events, so when deriving the replay script you must slice after the seedLength boundary — otherwise the parent’s chunks get replayed as if they were the child’s calls:

packages/test-support/llm-replay/src/index.tslines 532–542
    const text = readFileSync(childFile, 'utf8')
    const header = parseSessionHeader(text)
    // Derive the child's script from its own events only — events AT OR after the seed
    // boundary.
    const ownEvents = parseSessionLog(text).slice(header.seedLength)
    children.push({
      recordedId: header.id,
      createdAt: header.createdAt,
      entries: deriveReplayScript(ownEvents),
      primary: false,
    })
Source snapshot note: Based on the local deepseek-harness-master repo; verified against packages/test-support/llm-replay/src/index.ts, verified on 2026-08-13. Code blocks keep the original source text.

Cross-platform discipline follows from here too: checked-in fixtures must replay on both macOS and Linux; if a recorded snapshot fails on either platform, fix the fixture itself. The exact words on AGENTS.md line 123 are “fix fixtures, not normalizers”: fix the fixture, don’t write a normalizer. Normalizers pad cotton between tests and reality — pad enough and you stop testing reality.

How it works · A server built to trick LLM clients

Replay tests that behavior stays put — one piece still missing: the transport layer’s creative ways to die. Connection refused, socket reset mid-send, clean close without [DONE], rate limit with Retry-After, or just hanging still — each looks different to the adapter and recovery layers. In-process mocks miss them all, because mocks bypass real boundaries like fetch, SSE framing, socket teardown, and idle watchdogs. So DSH built dsh-llm-mock-server: a real Node HTTP server speaking OpenAI dialect, behaviors fully script-controlled, one behavior consumed per request, explicit error when the script runs out (design motive in Agent Note 2026-07-25-scriptable-llm-wire-fault-server.zh.md). Developers who want to reproduce faults by hand just point any app at a new base URL and key.

It also has a random mode that draws faults by weight for stress testing, with a public reproducible seed. The default weight table is itself a checklist of what LLM clients meet in the wild: plain success 48, slow success 10, mid-stream cut (partial_disconnect) 10, then 5 each for connection reset, disconnect, empty reply, and rate limit, server error 4, hit max_tokens / hang / 503 at 2 each, and the two meanest at 1 each: partial_eof (stream ends cleanly but unfinished) and malformed_json (bad JSON). Source comments remind you: this is tunable test pressure, not an estimate of production incident rates.

Source: DEFAULT_MOCK_LLM_RANDOM_WEIGHTS in packages/test-support/llm-mock-server/src/index.ts lines 56–70, verified on 2026-08-13.

The server’s discipline is restrained: it only reports protocol-layer facts, never whether to retry — policy belongs to the harness. Real combination tests send requests through the DeepSeek adapter, agent loop, and retry plugin in order, and check concrete things: exact request counts, numbered retry steps, failed half-chunks never leaking into history, and half-output with clean EOF classified as STREAM_CLOSED with no retry by default.

How it works · Property-based testing, blood on the first shot

The last weapon fights interleavings nobody thought of. Protocol-shaped code (chunk streams, event logs, inbox scheduling) has a combinatorially exploding input space; example tests can only pin cases you already imagined. DSH gives every protocol-shaped package a fast-check–driven property test: generators build realistic but adversarial inputs (duplicate indexes, lagging chunks, malformed streams missing block-start), and assertions target invariants, not concrete outputs — e.g. assembled block count can’t exceed distinct indexes seen, and repeated calls must be stable. Failures automatically print a reproducible seed.

Its track record opens the Agent Note’s first line:

“The property-test suite found a real BlockAssembler duplicate block-end bug on its first run.” A repeated block-end at the same index rewrites an already-finished block — and that bug survived under 100% happy-path line coverage. Source: .agents/notes/implemented/testing/2026-06-11-property-based-testing.zh.md, verified on 2026-08-13. The post-fix “first close wins” defense is in the source panel of the LLM Adapter Layer lesson.

This infrastructure has a side product: the official benchmark path in BENCHMARK.md is the Python SDK plus the minimal variant, each task in its own workspace. Once the test system is solid, running evals is just swapping the input.

Coverage is necessary, not sufficient

100% per file is a CI gate, but it only proves lines ran. Real bugs hide in interleaved sequences — property testing’s turf — or at transport boundaries — the fault server’s turf.

Fixtures are session logs

Replay fixtures aren’t hand-crafted mocks — they’re production-format session.jsonl itself. Record once, replay everywhere; it must pass cross-platform. If it fails, fix fixtures, not normalizers.

The fault server does no policy

It only honestly cuts, rate-limits, and hangs by script; whether to retry is the harness’s job. Keep test infrastructure neutral so it can testify for the adapter, loop, and retry layer at once.

Side-by-side · How others test

Grok Build: scriptable mocks too, but stops at the HTTP layer

Grok Build’s xai-grok-test-support ships a MockInferenceServer (crates/codegen/xai-grok-test-support/src/mock_server.rs): default echo mode echoes the last user message; it supports path-queued scripted responses (exact status, body, and SSE events); one server serves chat-completions, responses, and messages dialects at once; every request is fully logged with headers for assertions. The idea shares DNA with DSH’s fault server. The gap is coverage: from verified source, it scripts at the HTTP response layer; DSH’s fault server goes one layer deeper — socket reset, mid-send cut, hang — all enter the behavior vocabulary, plus a reproducible weighted random mode. On replay, Grok uses xai-sqlite-journal for persistence, but on public evidence there’s no equivalent that derives a replay script straight from production logs.

Claude Code: a closed-source testing black box

Visible test traces in restored-src are limited — which fits restoration: what you reverse from artifacts is product code; test code was never shipped with the artifact. So only one conclusion holds: on public evidence, outsiders can’t assess what Claude Code’s test system looks like. That contrasts an open-source harness value: DSH’s test strategy, coverage gates, and fixture discipline all live in the repo — the test infrastructure itself is a deliverable you can learn from and reuse.

Classroom Exercise
01

Design an invariant of your own

Suppose you’re adding another property test for BlockAssembler. The generator randomly emits interleaved legal and malformed chunk streams (duplicate block-end, missing block-start, lagging delta). Using this lesson’s assembled-block-count invariant as a reference, write two more invariants you think are worth asserting, and say which real failure class each one guards. Then reason through: a recorded snapshot fixture passes on macOS but fails on Linux because of path-separator diffs — under “fix fixtures, not normalizers,” what do you change, and why don’t you normalize paths away inside the comparator?

Takeaway: The way to test a nondeterministic system is to pin nondeterminism at the moment of recording: session logs derive replay scripts directly, and fixtures are production-format logs themselves. Transport deaths get rehearsed one by one on a real HTTP fault server; interleaving space is swept by property-based testing, which caught a real bug the coverage gate had passed on its first fight. Coverage proves code ran; only these three weapons prove the code is right.