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:

  1. inject = ["tools"] ensures the tool service is fully initialized before apply executes
  2. parameters automatically runs type validation and required field checks before invoking execute
  3. Values returned by execute must strictly conform to the output schema; throw exceptions for runtime failures
  4. 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:

  1. Built-in and third-party bundle defaults
  2. dsh.profile.bundles profile configuration
  3. $DSH_HOME/cordis.patch.yml global user configuration
  4. Runtime --patch parameters 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:

  1. Run fixed input invocations and validate output matches the defined output.schema
  2. Trigger invalid parameter inputs to confirm validation rejects bad requests
  3. Pass charsPerToken = 0 to confirm the tool throws errors instead of silent failure
  4. Modify source files to verify HMR reloads the plugin automatically
  5. 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