DSH Plugin Development Standard
This skill is the definitive standard for developing DeepSeek Harness (DSH) plugins: it converges conventions scattered across DSH's official documentation (tutorials, reference manuals, and generation catalogs) into an actionable workflow, hard rules, and a completion checklist. Everything in DSH is a plugin – model adapters, tool registries, session logs, and even the agent loop itself – so acting by this skill means you are extending DSH the same way it was architected.
Scope
- In-repository, file-based DSH plugin development: writing plugin packages, registering cordis.yml lines, patch overlays, developing tools, integrating models, packaging and installation.
- Workspace package development within the DSH monorepo (packages//) also falls under this skill (references/workspace-package.md).
- This skill does not cover: dynamic in-session plugins (cordis_define/cordis_run flow) and agent preset composition editing – these are covered by dedicated deployment skills or official tools.
Hard Rules (must be followed in every scenario)
- Interfaces are based on generated references. Service names, public methods, event signatures, and ctx keys must be based on the auto-generated subsystem pages and TypeScript interfaces from the repository; do not infer a full API from service names, examples, or old code, and do not maintain a separate static list.
- All contributions are side-effects. Everything you register through ctx (event listeners, tools, adapters, ctx.effect) is automatically revoked when the plugin is unloaded; do not create process-level or page-level side-effects in module scope; for third-party subscriptions that do not return a disposer, proactively find out the cleanup mechanism.
- Waterfall listeners must call next(). Not calling next() intentionally short-circuits downstream (used for interception/gateways) – it is not optional.
- Fail loudly. Throwing an exception in apply terminates the process; configuration validation failures must produce explicit errors; schemas should express complete constraints on their own – do not silently swallow errors at runtime.
- Declare required dependencies with inject, optional dependencies with ctx.get() and a null check. Do not use inject to bypass undefined checks; do not directly access ctx.xxx for unregistered services – unregistered services evaluated via the service resolver may yield undefined.
- All configurations must use Schemastery. Export
interface Configand a Schema with the same name, with default values defined in the schema; do not export a plain object as Config; any parameter that may vary across deployments must be in the config. - Tool execute returns canonical JSON values, not content blocks. Human-readable text goes in output.render; deployment strategies/hooks must not be built into the tool itself.
- Model-visible means already recorded. Any new model-visible input must fall within a mechanism that allows session logs to be reconstructed (via a new persistent event or through agent.inject()), with runtime invariant assertions.
- npm settings panels prefer
ctx.connection.rpc. Browser-side settings panels call main-process methods, preferring Connection RPC (ctx.connection.rpc.handle+ctx.connection.rpc.call); boilerplate is in references/connection-rpc.md. Bare@Remote, handwritten typert manifests, andcreateRequirepatching of harness source code are prohibited. Paths produced by the full Typert generator and exported viapackage.jsonas./typert+./remote, consumed byctx.remote.$mount(), remain valid.
Standard Workflows
Scenario A: Creating a New Plugin
- Determine what the plugin will contribute (service? tool? listener?), and use the "Placement of New Behaviors" table in references/seams.md to select the mechanism.
- Create src/.ts, exporting name, inject (optional), and apply(ctx, config?). Start with the function form; switch to a Service class form when you need to expose a service externally.
- Local development loop (source code checkout):
mkdir -p scratch-plugin/src
# scratch-plugin/cordis.yml -- patch overlay; plugin path must be absolute
- insert:
- id: hello
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
pnpm dsh web --patch ./scratch-plugin/cordis.yml # opens http://127.0.0.1:3080
- Verify loading and unloading: startup log appears, resources are properly cleaned upon deactivation (references/plugin-anatomy.md).
Scenario B: Adding a Tool to a Model
inject: ['tools'], use defineTool (name/description/parameters/output/execute).- Implement according to the execute convention in references/tools.md: schema DSL declares parameters and output, execute returns canonical values, respects exec.signal, optional presentationMeta/UI cards.
- When policies are needed, hang them on tools/* events (pre-execute/guard/execute/post-execute/result), do not hardcode policies into the tool body (see references/plugin-forms.md for an access-control example).
- Restart and let the model actually call the tool to verify.
Scenario C: Replaceable Capabilities (Multiple Providers)
- Split according to references/three-roles.md into three packages: Definition / Provider / Consumer (merge roles as needed, do not pre-emptively split).
- Definition owns Request/Result types and an abstract Service; Provider implements it; Consumer exposes it as a tool or a consumer of the service.
- In cordis.yml, list only the Provider and Consumer lines; swapping a provider only requires changing one line.
Scenario D: Integrating a New Model Provider
- Extend LlmAdapter to implement stream(), outputting chunks according to the StreamChunk protocol; use LlmError with a stable code for errors (or end in-band with finish { kind: 'error' | 'aborted' }); combine attributionHeaders() and pass signal for each HTTP request.
ctx.llm.registerAdapter(['<provider>'], adapter); optionally override resolveModel()/listModels().- In the composition, configure the agent-loop's provider/model for verification. See references/llm-adapter.md (includes 7 protocol obligations).
Scenario E: Packaging and Installation Delivery
- Create a composition package: package.json declares dsh.bundle (pointing to cordis.patch.yml), patch lines reference plugins by package name; library-only packages do not declare dsh.bundle.
- Install:
dsh plugin --profile <name> add <pkg>; first rundsh --profile <name> --dump-configto verify the layer, then start. - Remember layer order and full-line replacement semantics (references/packaging.md).
Scenario F: Creating a New Package in the DSH Monorepo
Follow the file-by-file checklist in references/workspace-package.md: create the package (package.json invariants/tsconfig/src/README) → register in root configuration → package topology and naming (role vocabulary) → README Model Experience structure → verification commands.
Decision Quick-Reference
| I want to… | Mechanism | Details |
|---|---|---|
| Expose capabilities to other plugins | Service (class form) | references/services.md |
| Consume existing capabilities | inject or ctx.get() | references/services.md |
| Inter-plugin communication / extension points | ctx.on / ctx.emit events (five dispatch modes) | references/events.md |
| Allow user configuration | Config + Schemastery | references/config.md |
| Add capabilities to a model | defineTool → ctx.tools.register (or raw JSON Schema) | references/tools.md |
| Connect a new model provider | LlmAdapter + ctx.llm.registerAdapter | references/llm-adapter.md |
| Build replaceable capabilities | Three roles (Definition/Provider/Consumer) | references/three-roles.md |
| Distribute a plugin | Composition package + profile | references/packaging.md |
| Look up built-in services / placement | seam directory, architecture map | references/seams.md |
| Scopes / service storage / Fiber API | ctx.extend/isolate/provide/mixin, fiber.* | references/context-api.md |
| Hooks / UI / panels / protocol bridge plugins | Five extension forms + function-to-mechanism map | references/plugin-forms.md |
| Browser-side panel calling main-process service | ctx.connection.rpc.handle/call, endpoint + envelope | references/connection-rpc.md |
| Create a new package / naming in the monorepo | Workspace package checklist + role vocabulary | references/workspace-package.md |
Pre-Completion Checklist
- Interfaces checked against generated references (service/event/ctx keys are based on generation), no API guessing from names.
- All registrations go through ctx (events/tools/adapters/effect), cleanable on unload; order-sensitive cleanup placed within the same ctx.effect.
- Required dependencies correctly declared via inject; optional dependencies have undefined-handling.
- Plugins with configuration: Config uses Schemastery, default values in schema, invalid configuration fails loudly on load.
- Tools (if any): execute returns canonical JSON values; render handles human readability; policies go through tools/* events; respects exec.signal; concurrency safety declared via isConcurrencySafe (not declared → exclusive).
- Waterfall listeners: call and return next() (unless intentionally short-circuiting).
- LLM adapters: StreamChunk protocol complete (chunk pairing, index based on first occurrence, usage before finish, arguments as raw JSON strings throughout); errors follow one of two valid paths; unsupported fields throw UNSUPPORTED; native replay uses finish.replayState; attributionHeaders and signal passed.
- Composition lines and layer order correct; new lines visible in --dump-config; no residual registrations after HMR/restart.
- If model-visible content changes: it falls within a mechanism that allows log reconstruction.
- Browser-side settings panels: prefer
ctx.connection.rpc.handle/call; bare@Remote, handwritten typert manifests,createRequirepatching of harness source code prohibited; full Typert generator +./typert+./remoteoutput remains valid. - New workspace package: package.json invariants, exactly one aggregate, naming conforms to role vocabulary, README has Model Experience structure, and constraints/typecheck/lint/build/hygiene all green.
Reference Files (load on demand, do not read all at once)
Full index and "when to read" table at references/README.md.
- references/plugin-anatomy.md – read when writing/modifying a plugin, handling lifecycle or HMR
- references/services.md – read when defining or consuming services, injecting dependencies
- references/events.md – read when communicating via events, listening to extension points
- references/config.md – read when making a plugin configurable
- references/context-api.md – read when using scopes, service storage, or the Fiber API
- references/three-roles.md – read when splitting replaceable capabilities
- references/tools.md – read when developing model tools
- references/llm-adapter.md – read when integrating a new model provider
- references/plugin-forms.md – read when writing hooks/UI/protocol bridge plugins
- references/connection-rpc.md – read when an npm settings panel needs to call main-process methods; also read when encountering bare
@Remote/ handwritten manifests /createRequiresource bridges - references/packaging.md – read when packaging, installing, or delivering a plugin
- references/workspace-package.md – read when creating a new package inside the monorepo
- references/seams.md – read when looking up built-in services, placement, or architecture maps
Validation
- Format self-check: name is kebab-case and matches the directory name; description is non-empty and concise (this skill is under 500 characters); body uses standard Markdown; all referenced reference files actually exist.
- Directory naming: this skill's name (name) is
dsh-plugin-dev, which differs from the hosting repository namedsh-plugin-dev-skills– after cloning/extracting, the folder must be renamed todsh-plugin-dev(matching name), otherwise loaders that address by directory name may fail to discover this skill. - In environments with the skills-ref CLI: run
skills-ref validate ./dsh-plugin-dev - Trigger regression: after modifying the description, run the eval set from evals/trigger-queries.json (method in the repository evals/README.md).
- Behavior validation: walk through Scenario A with a minimal plugin (load log + unload cleanup), then Scenario B with a greet tool.