Special Topic · Inside DeepSeek Harness

Testing a Nondeterministic System

Deterministic replay, property-based testing, and a fault server built to trick LLM clients

THE QUESTION THIS PAGE ANSWERS

ANSWER FIRST

What is the key idea behind “Testing a Nondeterministic System”?

Deterministic replay, property-based testing, and a fault server built to trick LLM clients

DECISION RULE

Make the claim earn its place. Use this page as a decision aid, not a definition to memorize. Connect the idea to one real task, one observable result, and one failure that would change your mind.

TRY NEXT

Write one question you could answer with evidence after trying this idea.

WATCH FOR

A conclusion that sounds complete but leaves the key assumption untested.

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.

The handoffs inside “Interactive demo · Fault-injection & replay lab”

“Play first, then talk.” shows that an Agent is not defined by the model alone. Each handoff between model, context, tools, state, permissions, and people affects both progress and recovery.

Write the state before adding capability

Starting from “There’s only one way to test a nondeterministic system: fence off the uncertain parts and make everything else deterministic.”, split the workflow into starting state, next action, tool result, state update, and stop condition. Debugging then means finding the first lost piece of information or authority instead of saying vaguely that the model “got worse”.

A happy path is not reliability

Use “Suppose you’re adding another property test for BlockAssembler .” to replay one successful and one failed run. Record the context, tool result, and owner at each turn; the workflow is maintainable when a second person can follow it without the original builder.

From “Interactive demo · Fault-injection & replay lab” to “How it works · Nail the foundation first”

“Interactive demo · Fault-injection & replay lab” grounds the problem in “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…”. “How it works · Nail the foundation first” then moves it toward “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…”. Together, they show that the lesson is not just a conclusion to remember, but a claim with conditions.

Carry the judgment into the next situation

When analyzing an Agent, trace state, action, tool result, and next step in order. Each handoff should explain where information came from, who confirmed it, and where failure stops.

  • “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…
  • “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…
  • “The closing point”: 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, o…

The final “The closing point” brings the discussion to “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, o…”. The useful thing to carry forward is knowing which judgments must be revisited when input, scale, or risk changes.

Mark as learned Your reading progress updates automatically
← PreviousNext →

Keep reading

The next useful article in the thread.

ARTICLE DISCUSSION

Leave one useful thought here.

Keep the idea that clicked, the question that stayed open, or a small note for the next learner.

Discussing Testing a Nondeterministic System Inside DeepSeek Harness
3discussionsArticle discussion · synced with the Circle
View in the learning circle
AM
Asha MorganContent editor
INSIGHTField note

I turned one judgment from this article into a small experiment I could run today. Knowing what to observe next is more useful than simply remembering the conclusion.

ARTICLE DISCUSSION7 helpful
LH
Lin HarperIndie developer
INSIGHTInsight

After reading this, I first looked for the conditions behind the idea instead of copying the method into a project. That order made the later trade-offs much clearer.

ARTICLE DISCUSSION5 helpful
KM
Kiki MooreProduct operations
QUESTIONQuestion

When this judgment reaches real work, which constraint should be added first? I am curious which step matters most between reading and the first practical attempt.

ARTICLE DISCUSSION4 helpful