DSH plugin capability roles: Definition, Provider, Consumer

Concepts & ArchitecturePublished 2026-10-17Author: DeepSeek Plugin Market
DeepSeek HarnessDSH pluginService DefinitionProviderConsumerseam
Why a general DSH plugin capability splits into Definition, Provider and Consumer packages, with a Bash-based three-step example and stopping rules.

When a DSH plugin capability is general enough, the official guidance splits it into three roles: a Service Definition for the contract, a Service Provider for the implementation, and a Consumer that exposes it. This is not architecture for its own sake — it makes swapping implementations a one-line configuration change, because callers only know the Definition. The test for splitting is equally clear: do the three roles need to evolve independently? If not, keep one package; if yes, split. Bash is the canonical example: dsh-shell is the definition, dsh-bash-local is the provider, and dsh-tool-bash is the consumer — together they form one seam.

DSH plugin capability roles: Definition, Provider and Consumer

Bottom line: the Definition owns the contract, the Provider owns the implementation, and the Consumer owns exposure; dependencies are one-way and the last two never depend on each other.

RoleResponsibilityDepends onBash example
Service DefinitionService plus request/result typesNothingdsh-shell
Service ProviderPerforms the capabilityDefinitiondsh-bash-local
ConsumerExposes it as a tool or commandDefinitiondsh-tool-bash
  1. Callers know only the contract: a Consumer injects shell and never cares whether execution is local or remote.
  2. Swaps happen at composition: changing a Provider is one line in cordis.yml; Definition and Consumer stay as they are.
  3. Roles are not packages: they are logical divisions, so one package may hold several roles or be split into three.

Why split a DSH plugin capability into three packages

Bottom line: three payoffs — swappable providers, independent evolution, and decoupled dependencies.

  1. Swappable providers: one Definition can have several Providers selected by configuration, with the calling convention unchanged.
  2. Independent evolution: the Definition rarely changes once callers depend on it, Providers can optimize performance and safety, and Consumers can change how a capability is presented to the model.
  3. Decoupled dependencies: Provider and Consumer never depend on each other, so changes on one side cannot break the other.
  4. Clearer diagnosis: when something breaks, identify which role failed — contract mismatch, implementation error, or exposure problem.

Three steps to build a DSH plugin capability

Bottom line: write the Definition with request/result types first, then the Provider, then the Consumer; the order matters.

  1. Write the Service Definition:
ts
import { Service, type Context } from '@deepseek-ai/cordis'

declare module '@deepseek-ai/cordis' {
  interface Context { myCap: MyCapService }
}

export abstract class MyCapService extends Service {
  constructor(ctx: Context) { super(ctx, 'myCap') }
  abstract execute(request: MyCapRequest): Promise<MyCapResult>
}

export interface MyCapRequest { input: string }
export interface MyCapResult { output: string }
  1. Write the Service Provider:
ts
class MyCapLocal extends MyCapService {
  async execute(request: MyCapRequest): Promise<MyCapResult> {
    return { output: request.input.toUpperCase() }
  }
}
export const name = 'my-cap-local'
export function apply(ctx: Context) { ctx.plugin(MyCapLocal) }
  1. Write the Consumer as a model-facing tool:
ts
export const inject = ['tools', 'myCap']
export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'my_cap',
    description: 'Execute my capability.',
    parameters: { input: { type: 'string', required: true } },
    output: { schema: { type: 'string' }, render: (_a, v) => [{ type: 'text', text: v }] },
    async execute(args) { return (await ctx.myCap.execute({ input: args.input })).output },
  }))
}
  1. Compose both rows: the provider package and the consumer package, in cordis.yml.
  2. Verify swappability: replace the provider with another implementation and change only that row; the tool name and call shape stay identical.

Where to stop splitting a DSH plugin capability

Bottom line: do not split preemptively — only when roles truly evolve independently is the extra package cost worth paying.

  1. A simple tool plugin needs no split: there is no alternative implementation, so splitting only adds release and dependency overhead.
  2. One provider that will not change soon: keeping Definition and Provider together is simpler.
  3. Too many packages: installation, version alignment and debugging all get harder; make one package work first.
  4. Split signals: a second Provider is needed, or the Consumer must be reused across scenarios.
  5. Team boundaries: when different people own different roles, separate packages unblock release cadence.

DSH plugin composition and swaps at the seam

Bottom line: composition happens in configuration, and swapping a Provider is a configuration change rather than a code change.

  1. Mount by package name: list Providers and Consumers in order inside cordis.yml.
  2. Swap one row: comment out the old Provider and add a package with the same contract.
  3. Verify: call the tool once and confirm the result still matches the Definition's Result shape.
  4. Check the seam reference first: built-in capabilities already have seam splits, so look before inventing a new one.

DSH plugin troubleshooting around the three roles

Bottom line: ask which layer broke before asking where the code is wrong.

  1. "Service missing" — check that the Provider is mounted and inject is spelled correctly.
  2. Type mismatch — ensure request/result types exist in exactly one place: the Definition.
  3. Behavior changed after a swap — the implementation violated the contract; align it back to the Definition.
  4. A Consumer instantiating the implementation — that breaks the dependency direction and defeats swapping.
  5. Splitting too early — ship one package, then split when the need is real.
  6. Repair status, stated honestly: the roles, dependency direction and three-step example follow the official Three capability roles page; confirm defineTool fields and Service signatures against the official reference for your version.

Sources: Three capability roles, Services and dependencies, Capability seams.

FAQ

What do the DSH plugin Service Definition, Provider and Consumer roles each own?

In DSH plugin design, the Service Definition owns the service contract and request/result types, the Provider implements the capability, and the Consumer exposes it to the model or other plugins. Callers depend only on the Definition, so swapping a Provider leaves the Definition and Consumer untouched.

Does a DSH plugin capability have to be split into three packages?

DSH plugin roles need splitting only when they evolve or are replaced independently; splitting merely to look architectural is over-design. DSH plugin authors should ask whether the Provider and Consumer will actually change separately — if not, one package is easier to maintain.

Which files change when I swap the DSH plugin capability provider?

Swapping a DSH plugin Provider normally changes one line of composition configuration; the Definition and Consumer stay the same. That is the core payoff of separating the three roles: replacing an implementation never touches the calling convention.

Which DSH plugin role should own the request and result types?

The DSH plugin Service Definition owns the request and result types, and both Provider and Consumer depend only on that package. A single type source prevents the two ends from drifting apart.

Can the three DSH plugin roles depend on each other and create a cycle?

No: DSH plugin dependencies are one-way — Provider and Consumer each depend on the Definition, while Provider and Consumer never depend on each other. That direction is what makes swapping either role safe.

Related Terms

Service Definition
Service Definition is the contract package of a DSH plugin capability, declaring the Cordis service and its request and result types. Provider and Consumer both depend only on it, making it the slowest-changing layer.— https://deepseek-harness.github.io/deepseek-harness/develop/practice/
Service Provider
Service Provider is the implementation package of a DSH plugin capability and performs the actual work. One Service Definition can have several Providers, selected through composition configuration.— https://deepseek-harness.github.io/deepseek-harness/develop/practice/
Consumer
A Consumer exposes a DSH plugin capability to the model or other callers, usually as a tool or command. It depends only on the Service Definition, never directly on a Provider.— https://deepseek-harness.github.io/deepseek-harness/develop/practice/
capability seam
A capability seam is the overall boundary formed by the three roles of one capability; no single role is the seam. It determines where an implementation can be replaced.— https://deepseek-harness.github.io/deepseek-harness/reference/capability-seams

Sources