Introduction
DeepSeek Harness plugin development enables developers to integrate custom tools, model adapters, policy and UI capabilities into the runtime powered by Cordis. This tutorial is built on DeepSeek Harness version 0.1.1-rc.2, released on August 21, 2026. Readers will learn to build a complete workflow, from defining tool logic, registering dependencies, automatic cleanup, configuration layering, to packaging and releasing bundles. By the end of this guide, you will produce a reusable plugin that can be directly invoked locally or shared with other developers.
The core runtime component, Cordis Context Container, acts as the central hub connecting tool plugins, LLM adapters, agent loops, and bundle profiles. Plugins are injected into the shared context at startup, with built-in lifecycle management for dependency registration and clean unloading. DeepSeek opened the developer preview of Harness in August 2026, with the current stable release tagged 0.1.1-rc.2.
Unlike static prompt engineering, plugins inject real executable capabilities into agent workflows. The model adapters, tool registries, session logging and agent loop within Harness are themselves implemented as plugins, meaning bundle deployment rarely requires modification of the Harness core source code. When managing heterogeneous LLM endpoints for agent workflows, teams can leverage an API gateway to standardize access control and routing. Treerouter serves this purpose for unified model traffic governance.
Core Concept Distinction: Plugin, Tool, Bundle and Profile
Many new developers confuse these foundational terms in the Harness ecosystem. The following table clarifies definitions, core interfaces and invocation patterns:
| Concept | Definition | Key Interfaces | Directly Invoked by Model |
|---|---|---|---|
| Plugin | Defines how capabilities are injected into runtime | apply(ctx), inject |
Not necessarily |
| Tool | Defines actionable functions the model can execute | ctx.tools.register() |
Yes |
| Bundle | Packages a collection of plugins and configurations | package.json, dsh-bundle, cordis.patch.yml |
No |
| Profile | Defines which bundles activate on startup | $DSH_HOME/profiles/<name> |
No |
| MCP Service | Exposes external tools via MCP protocol | MCP server + MCP client plugin | Yes |
| Skill | Injects prompts and resources for model use | Skill provider + skill tool | Indirectly |
A practical rule of thumb for design decisions: use a tool plugin when you only need to add new callable actions. Choose an LLM Adapter if you intend to integrate new model providers. When multiple plugins and default configurations need versioned distribution, package everything as a Bundle.
Pre-development Preparation: Version Lock and Runtime Environment
Aligning local development environments with the official release version is critical to avoid breaking changes introduced by preview API updates. The baseline environment used in this tutorial is listed below:
| Project | Adopted Value | Source |
|---|---|---|
| DeepSeek Harness | 0.1.1-rc.2 | Official repo package.json, tag dsh-v0.1.1-rc.2 |
| Node.js | 22.19+ or 24+ | Official development guide, compatible with 22.19, 24, 26 |
| pnpm | 11.7.0 | Official repo packageManager field |
| Default Web UI | http://127.0.0.1:3080 | Official README |
| Open Source License | MIT | Official repository LICENSE |
Environment Validation Commands
Before starting development, verify local dependencies via the following shell commands:
node --version
corepack enable
pnpm --version
git --version
If you only want to run pre-built plugins without source-level development, launch the Web UI directly:
npx @deepseek-ai/dsh@0.1.1-rc.2 web
For plugin development and debugging, clone and build the matching source repository:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
git checkout dsh-v0.1.1-rc.2
pnpm install
pnpm run build
pnpm dsh web
Visit http://127.0.0.1:3080 after startup. The official documentation recommends running pnpm run typecheck immediately after installation to validate type consistency. Without a valid DeepSeek API Key, the Cordis demo can load normally, but real agent conversations will not complete.
Step 1: Build a Minimal Working Tool Plugin
A minimal Harness tool plugin only needs to declare tools dependencies and register a tool with name, parameter schema, output schema and execution logic. We will implement text-stats, a simple tool that counts characters, non-blank lines and estimates token consumption for incoming text.
Run these commands in the root of the DeepSeek Harness repository:
mkdir -p scratch-plugin/src
Create scratch-plugin/src/index.ts with the following TypeScript implementation:
import type { Context } from "@deepseek-ai/cordis";
import { defineTool } from "@deepseek-ai/dsh-tools";
export const name = "text-stats";
export const inject = ["tools"];
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: "text-stats",
description: "Count characters, non-blank lines and estimate token usage of input text",
parameters: {
type: "object",
properties: {
content: { type: "string", description: "Raw text to analyze" }
},
required: ["content"]
},
output: {
type: "object",
properties: {
characters: { type: "number" },
lines: { type: "number" },
estimatedTokens: { type: "number" }
}
},
async execute({ content }) {
const lines = content.split("\n").filter(line => line.trim() !== "").length;
const characters = content.length;
const charsPerToken = 4;
const estimatedTokens = Math.ceil(characters / charsPerToken);
return { characters, lines, estimatedTokens };
}
}));
}
Four non-negotiable contracts govern the implementation:
inject = ["tools"]ensures the tool service is fully initialized beforeapplyexecutesparametersautomatically runs type validation and required field checks before invokingexecute- Values returned by
executemust strictly conform to theoutputschema; throw exceptions for runtime failures - Registration binds with plugin Fiber. The tool will auto-register when the plugin loads and unregister on disposal
The charsPerToken constant here is only for demonstration purposes and is not a production-grade tokenizer. Production plugins requiring precise billing calculation should integrate the tokenizer matching the target LLM provider.
Step 2: Load Local Plugins via Patch
Local development uses cordis.patch.yml to inject plugins without publishing packages to npm.
Create scratch-plugin/cordis.yml:
- insert:
- id: text-stats
path: ./scratch-plugin/src/index.ts
Run the patched Harness instance from the repository root:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
Navigate to the Web UI and send the test prompt:
Please use text-stats to analyze the following paragraph:
DeepSeek Harness is a plugin runtime. Everything is a plugin.
If the integration works correctly, the model will invoke text-stats and return a JSON payload containing characters, lines and estimatedTokens. This confirms registration, parameter validation, execution and response parsing function as expected.
The local plugin development workflow can be summarized in six phases: create module → declare inject dependencies → register tool → load via cordis patch → trigger calls from Web UI → validate returned results.
Step 3: Understand Dependency Injection and Plugin Lifecycle
Cordis manages plugin loading order automatically based on declared dependencies, ensuring no residual registrations after hot updates. If your plugin depends on tools, llm or sessions, declare these within the inject array. Plugins will reload automatically after dependent services restart. Event listeners, tool registrations and adapter bindings are tracked by Fiber. Any manually allocated resources must be released inside ctx.effect() to prevent memory leaks.
Sample code for lifecycle cleanup:
import type { Context } from "@deepseek-ai/cordis";
export function apply(ctx: Context) {
const timer = setInterval(() => {
ctx.logger.info("text-stats plugin is alive");
}, 5000);
return () => clearInterval(timer);
}
If multiple asynchronous cleanup steps exist, sequence them inside a single disposer function, as separate disposers execute concurrently.
Custom Configuration with Schema
When your plugin requires adjustable parameters such as timeouts, API endpoints or retry thresholds, define them using Schemastery rather than hardcoding values.
import Schema from "@deepseek-ai/schemastery";
export interface TextStatsConfig {
charsPerToken: number;
}
export const config = Schema.object({
charsPerToken: Schema.number().default(4),
});
Invalid configuration will cause plugin loading failure. After modifying cordis.yml, HMR will roll back old registrations and reload the plugin with updated configuration automatically.
Step 4: Package into Installable Bundles
The final deliverable is a dsh-bundle, a npm package containing manifests and patch configurations for distribution. We will build a production-ready JavaScript package converted from the validated TypeScript source.
The target directory structure is as follows:
text-stats/
├── package.json
├── cordis.patch.yml
└── index.js
package.json definition:
{
"name": "dsh-text-stats",
"version": "0.1.0",
"description": "Text statistics tool plugin for DeepSeek Harness",
"files": ["index.js", "cordis.patch.yml"],
"dependencies": {
"@deepseek-ai/dsh-tools": "0.1.1-rc.2"
}
}
index.js (production JavaScript build):
import { defineTool } from "@deepseek-ai/dsh-tools";
export const name = "text-stats";
export const inject = ["tools"];
export function apply(ctx) {
ctx.tools.register(defineTool({
name: "text-stats",
description: "Count characters, non-blank lines and estimate token usage of input text",
parameters: {
type: "object",
properties: { content: { type: "string" } },
required: ["content"]
},
output: {
type: "object",
properties: {
characters: { type: "number" },
lines: { type: "number" },
estimatedTokens: { type: "number" }
}
},
async execute({ content }) {
const lines = content.split("\n").filter(line => line.trim() !== "").length;
const characters = content.length;
const estimatedTokens = Math.ceil(characters / 4);
return { characters, lines, estimatedTokens };
}
}));
}
cordis.patch.yml:
- insert:
- id: text-stats
path: ./index.js
Install Bundle to Profile
Use the Harness CLI to add the bundle to a profile:
npx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add ./text-stats
npx @deepseek-ai/dsh@0.1.1-rc.2 web --profile web
npx @deepseek-ai/dsh@0.1.1-rc.2 web --dump-config
Verify that text-stats appears in the --dump-config output. To remove the bundle later:
npx @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web remove dsh-text-stats
Profile Configuration Priority
Harness applies configuration layers in ascending priority order:
- Built-in and third-party bundle defaults
dsh.profile.bundlesprofile configuration$DSH_HOME/cordis.patch.ymlglobal user configuration- Runtime
--patchparameters for ad-hoc testing
Configurations from higher priority layers override lower ones. A single config object is merged entirely instead of partial deep merging.
Step 5: Testing, Coverage and Release
Plugin validation must cover normal input, edge cases, invalid payloads, uninstall workflows and package installation artifacts. The recommended checklist:
- Run fixed input invocations and validate output matches the defined
output.schema - Trigger invalid parameter inputs to confirm validation rejects bad requests
- Pass
charsPerToken = 0to confirm the tool throws errors instead of silent failure - Modify source files to verify HMR reloads the plugin automatically
- Execute
pnpm pack, then install from the local tarball in a clean profile and re-test
Common Fault Diagnosis Table
| Symptom | Root Cause | Resolution |
|---|---|---|
| Plugin not detected at startup | Incorrect patch path or invalid yaml syntax | Validate file path and yaml formatting |
| Tool not visible in model context | Failed dependency injection, missing inject declaration |
Inspect --dump-config and runtime logs |
output.schema validation failure |
Return value type mismatch | Align return payload strictly with schema definition |
| HMR fails after modification | Missing disposer or unclosed async resource | Implement proper cleanup in disposer |
When installing packages from GitHub, pnpm skips dependency builds by default. You should explicitly allow build scripts in package.json and pin commit SHA for stability. Installation scripts run inside the agent sandbox, which requires careful security auditing.
Integrating Model API Endpoints
Model endpoint configuration belongs to LLM adapters instead of business tool plugins. Developers configure DEEPSEEK_API_KEY and base URL in the profile.
Example environment variable setup for OpenAI-compatible endpoints:
export DEEPSEEK_API_KEY="YOUR_API_KEY"
export DEEPSEEK_BASE_URL="https://treerouter.com/v1"
If you switch to a custom model provider, define the adapter configuration inside the profile patch. Model identifiers must use exact official model IDs, not aliases.
Extension Decision Framework: Tool, Hook or LLM Adapter
Use this matrix to select the right extension type for your requirements:
| Requirement | Recommended Extension | Core API |
|---|---|---|
| Add new callable actions for models | Tool Plugin | ctx.tools.register() |
| Add pre/post processing for tool invocations | Hook | tools.beforeCall / tools.afterCall |
| Inject persistent prompt instructions | Skill | Skill provider registration |
| Connect new LLM providers | LLM Adapter | Service definition + provider consumer |
| Expose external third-party tools | MCP Client Plugin | MCP client integration |
LLM adapters enforce strict streaming protocol specifications. Every block-start must pair with block-end, usage metrics must be fully populated, and finish signals must fire at the end of every response.
Conclusion
DeepSeek Harness provides a flexible plugin-first runtime built around the Cordis context container. The iterative development flow starts with local --patch testing, validates lifecycle behavior with HMR, packages distributable bundles, and manages environment isolation via profiles. All capabilities, including model adapters and core agent logic, are implemented as plugins, so teams can extend agent functionality without modifying Harness core source.
This guide is built based on Harness version 0.1.1-rc.2 and official documentation released in August 2026. The framework evolves rapidly, so developers are recommended to recheck bundle and plugin specifications within 90 days. For production agent systems connecting multiple LLM backends, standardized routing infrastructure reduces operational overhead.
Learn more:https://treerouter.com






