跪拜 Guibai
← All articles
Agent · AI Programming · LangChain

A Step-by-Step Build of a DeepSeek Harness Plugin, from Tool Definition to HMR Debugging

By 怕浪猫 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Plugin systems that enforce typed configuration, explicit dependency injection, and whole-row config replacement eliminate silent misconfiguration — a class of bug that wastes hours in looser frameworks. The git-install security warning about `allowBuilds` is a concrete supply-chain concern any developer distributing agent plugins should address before shipping.

Summary

The DeepSeek Harness plugin system runs on Cordis, a dependency-injection and event framework. A minimal tool plugin starts with `defineTool`, `inject`, and `apply`, registering a callable function that the model can invoke. Configuration is handled through Schemastery schemas — not optional JSON blobs but validated, typed objects that fail loudly on mismatch, with `!!js` tags available for runtime-computed values. Event listeners like `tools/result` fire synchronously during result materialization, before the tool's promise resolves, letting observer plugins log or react without the tool knowing they exist. Packaging a plugin into a bundle requires a `package.json` with a `dsh.bundle` declaration and a `cordis.patch.yml` that maps plugin ids to npm package names. Installing into a profile layers configuration in a strict order — bundle patches, profile patch, machine-level patch, then CLI overlays — and each layer replaces entire config rows by id, never deep-merging. A git-install pitfall means TypeScript plugins need a self-contained `prepare` script, and pnpm >=10 users must explicitly allow build scripts for git dependencies. The HMR loop works when plugins carry explicit ids, dependent services are present, and the runtime uses `tsx` for direct TypeScript execution.

Takeaways
A dsh tool plugin exports `name`, `inject`, and an `apply` function that registers the tool via `ctx.tools.register(defineTool({...}))`.
Configuration uses Schemastery schemas — a separate `export const Config` that Cordis validates; invalid config causes the process to exit with code 1.
The `!!js` YAML tag computes config values at runtime, available only in `config` and `disabled` fields.
Event listeners import type declarations via `import '@deepseek-ai/dsh-tools'` to get typed events, then register with `ctx.on('tools/result', ...)`.
The `tools/result` event fires synchronously before the tool's promise resolves, so loggers see results first.
A bundle package declares `dsh.bundle` in `package.json` with a `patch` pointer to `cordis.patch.yml`, and the patch references plugins by npm package name, not relative path.
Configuration layers load in order — bundles, profile, `$DSH_HOME`, then `--patch` overlays — and later layers replace entire config rows by id, never deep-merging.
Installing a TypeScript plugin from GitHub requires a `prepare` build script in the plugin's `package.json` and an `allowBuilds` entry in the profile's `pnpm-workspace.yaml`.
HMR requires explicit ids on patch entries, all injected services to be available, and a `tsx` runtime for TypeScript.
A plugin checklist covers exported name/inject/Config, effect-API registration, `ctx.effect` wrapping for external resources, service name prefixing, declaration merging, and test coverage.
Conclusions

The 'fail loud' design — exiting with code 1 on config validation failure rather than running with defaults — is a deliberate safety choice that prevents misconfigured agents from operating silently.

Whole-row config replacement instead of deep merging forces explicitness: changing one field requires rewriting the entire config row, which eliminates ambiguity about which layer owns which value.

The `tools/result` event firing before the promise resolves is a subtle ordering guarantee that lets observer plugins act on results without race conditions relative to the caller.

The git-install `allowBuilds` warning frames a real supply-chain tradeoff: convenience of direct GitHub installs versus the risk of executing arbitrary build scripts outside any agent sandbox.

The checklist item 'no hardcoded tunable values' encodes a testable boundary — if `cordis.yml` cannot change a value without editing code, that value is not properly configurable.

Concepts & terms
Cordis
The dependency-injection and event framework underlying DeepSeek Harness. Plugins declare dependencies via `inject` and communicate through typed events.
Schemastery
A schema definition and validation library used for plugin configuration. Schemas are exported as `export const Config` and validated at load time; failures terminate the process.
defineTool
A helper from `@deepseek-ai/dsh-tools` that converts a parameter spec into JSON Schema, infers argument types, and produces a tool definition ready for registration.
HMR (Hot Module Replacement)
A development feature that unloads and reloads plugin code on file save without restarting the process. Requires explicit ids, available injected services, and a tsx runtime for TypeScript.
Profile
A runnable composition of dsh defined by a list of bundles in `package.json`. Profiles layer configuration from bundles, a profile-level patch, a machine-level patch, and CLI overlays.
Bundle
A distributable plugin package containing plugin code, a `package.json` with a `dsh.bundle` declaration, and a `cordis.patch.yml` configuration layer.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗