learn-deepseek-harness
August 28, 2026 · View on GitHub
learn-deepseek-harness
Everything is a plugin: rebuild DeepSeek Harness from scratch.
DeepSeek Harness (dsh) is a production agent harness: a large TypeScript codebase built on Cordis, where everything is a plugin. Reading it from top to bottom is difficult because its core design is distributed across many packages.
This tutorial takes a different approach. You will rebuild a minimal version, Mini-dsh, using only Python's standard library. The material is organized into 14 sections across four phases. Each section adds one mechanism, verifies it with an offline check, and links it to the corresponding implementation in dsh.
Contents: Big picture · How to learn · Sections · Repository structure · Running · Contributing · References
Big picture
Everything you build, on one page: the kernel that mounts it, the loop that runs it, and the seams it reaches through to touch anything outside the log.

One rule carries through every section because the real system is built on it:
Everything is a plugin, and every registration is reversible.
How to learn
Every section follows the same four-part structure:
- Opening: the design question the section answers before introducing any code.
- Mechanism: the components you will build, explained with excerpts and a flow diagram.
- In real dsh: a table that maps Mini-dsh symbols to their dsh counterparts. The links are pinned to the studied version and note the production features omitted from this tutorial.
- Failure modes: what breaks when the mechanism is missing, not only what works when it is present.
Read the sections in order. Each one copies the previous src/ directory unchanged and adds one mechanism. Run the section's offline check as you go. To examine a mechanism in isolation, compare two adjacent src/ directories; the diff contains only the new mechanism.
Sections
| # | Section | Design question | Mechanism |
|---|---|---|---|
| Foundation | |||
| 00 | Setup | Why should Mini-dsh use its own Message format behind a swappable model interface? | provider-agnostic Message, streaming model interface, scripted stand-in |
| 01 | Kernel | Why should the framework own plugin cleanup? | fiber/effect reversible registrations |
| 02 | Session log | Why derive model history from a log instead of storing a message list? | append-only log + surface + deriveMessages() |
| 03 | Compaction | If the log is append-only, how does compaction remove anything the model sees? | surface replace op |
| The Loop | |||
| 04 | Agent loop | Why reassemble the prompt and rederive history before every step? | turn/step state machine, with the log as the only durable state |
| 05 | Tools | Why should a denied or failed call still produce a normal tool/result? | scoped registry + pre/ask/guard/execute/post pipeline |
| 06 | Scheduler | Why do parallel-safe calls overlap, exclusive calls form barriers, and unstarted calls receive synthetic results after cancellation? | four-stage parallel tool scheduler |
| 07 | Inbox | Why use two inbox targets and claim messages only at step boundaries? | next-turn/next-step steering |
| 08 | System prompt | Why represent dynamic state as a re-emitted user message instead of system text? | ordered providers -> system text + tool list + runtime-context snapshot |
| 09 | Skills | Why inject a catalog as context but load full skill content through a tool call? | layered provider registry; catalog injected, bodies loaded on demand |
| Capabilities | |||
| 10 | Capability seams | When does a capability justify the three-way split? | Definition/Provider/Consumer ABCs (fs/shell/sandbox/llm) |
| 11 | Jobs | Who owns cancellation after a job ID is published? | owner-fenced background-work protocol |
| 12 | Subagent | Why should subagents use a provider interface instead of inheriting from Agent? | named-provider delegation registry |
| Composition | |||
| 13 | Composition | Why should a patch replace the entire configuration instead of deep-merging it? | ordered patch layers over an empty entry list |
Repository structure
learn-deepseek-harness/
├── README.md
├── LICENSE
├── requirements.txt # live demos only: anthropic, python-dotenv
├── .env.example # ANTHROPIC_API_KEY / ANTHROPIC_MODEL / optional base URL
├── assets/ # the architecture diagram, and the script that draws it
└── sections/
├── 00-setup/
│ ├── README.md
│ └── src/ # message.py, standin.py, test.py
├── 01-kernel/
│ ├── README.md
│ └── src/ # 00's src verbatim + kernel.py, test.py
├── ...
└── 13-composition/
├── README.md
└── src/ # 12's src verbatim + this Mechanism, test.py, demo.py
Running
The offline checks verify each mechanism. They use only Python's standard library, require no API key or network connection, and produce deterministic output.
python sections/00-setup/src/test.py # one section
for t in sections/*/src/test.py; do python "$t" || break; done # all sections
Sections 04 and later also include a live demo that runs scripted turns against the Anthropic API. The script exits cleanly if no API key is configured.
pip install -r requirements.txt
cp .env.example .env # then fill in ANTHROPIC_API_KEY
python sections/04-agent-loop/src/demo.py
Contributing
- Improve a section: add a clearer excerpt, a more useful failure mode, or a stricter check for an existing mechanism.
- Correct the record: a mini-to-real mapping or claim about dsh that the pinned source contradicts.
References
- Cordis primer: dsh's own intro to the plugin runtime it is built on.
- Cordis tutorial: writing real dsh plugins; this tutorial defers all plugin-authoring how-to there.
- Subsystem docs: design documentation for each subsystem, corresponding to the "In real dsh" section in each chapter.
- cordiverse/cordis: the upstream framework dsh vendors.