DSH Capability Seams: The Design Pattern Behind Everything-Is-a-Plugin
How DeepSeek Harness makes everything-is-a-plugin actually work. Deep dive into Capability Seams: the three-role pattern (Definition, Provider, Consumer) that lets you swap any component.
TL;DR: “Everything is a plugin” is easy to say, hard to architect. DeepSeek Harness solves it with Capability Seams — a three-role pattern where each capability splits into a Service Definition (the contract), a Service Provider (the implementation), and a Consumer (typically a model-facing tool). The provider and consumer never depend on each other — only on the definition. Swap one provider and the entire downstream stack follows. This is how one config change moves bash, PTY, and LSP to a remote sandbox simultaneously.
The Problem With “Everything Is a Plugin”
Every plugin framework claims extensibility. Few deliver on it at the system level. The typical failure mode: plugins can add features but can’t replace core behavior without forking. You can add a new tool, but can you swap how all tools execute? You can add a model adapter, but can you change how the execution sandbox works without touching ten other packages?
DeepSeek Harness solves this with a specific architectural pattern they call Capability Seams. It’s not a marketing term — it’s a structural design enforced by the package graph. Understanding seams is understanding why DSH’s “everything is a plugin” claim actually holds up under inspection.
The Three-Role Pattern
A capability seam consists of exactly three roles, typically in separate packages:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Service │ │ Service │ │ Consumer │
│ Definition │◀──────│ Provider │ │ (tool/policy) │
│ (contract) │ │ (implementation) │ │ │
└────────┬────────┘ └──────────────────┘ └────────┬────────┘
│ │
└─────────────────────────────────────────────────────┘
Consumer injects the Definition,
never the Provider directly.
Service Definition — Declares the interface on ctx.<key>. Owns the TypeScript types, error codes, and event names. Changes rarely after consumers depend on it.
Service Provider — Implements the interface. Can be swapped by changing one line in cordis.yml. Multiple providers can exist (local, Docker, remote, E2B).
Consumer — Uses the capability, typically to expose it as a model-facing tool. Depends only on the Definition, never on any specific Provider.
The critical constraint: Provider and Consumer do not depend on each other. This is what makes the swap possible — you can replace the provider without the consumer knowing or caring.
A Concrete Example: Bash Execution
The packages/ directory in DSH — each capability seam is split across definition, provider, and consumer packages.
The Bash capability in DSH consists of three packages:
| Role | Package | What It Does |
|---|---|---|
| Definition | dsh-shell | Defines ctx.shell service, bash request/result types |
| Provider | dsh-bash-local | Executes commands on the local machine |
| Consumer | dsh-tool-bash | Exposes bash as the model-callable bash tool |
# cordis.yml — swap the provider, everything else stays
- name: '@deepseek-ai/dsh-bash-local'
# Replace with:
# - name: '@deepseek-ai/dsh-bash-sandbox' # sandboxed execution
# - name: '@deepseek-ai/dsh-bash-docker' # Docker container
# - name: '@deepseek-ai/dsh-bash-remote' # remote machine
The model sees the same bash tool with the same schema. The consumer (dsh-tool-bash) doesn’t change. Only the provider row changes in config.
Why One Swap Moves Everything
Here’s where the design gets powerful. The subprocess seam (ctx.subprocess) sits beneath multiple consumers:
- The bash executor uses it for collected batch output
- LSP uses it for raw protocol pipes to language servers
- The PTY backend uses it for terminal sessions
- The ACP subagent backend uses it for piped ndjson
All of these share one execution world. Point ctx.subprocess at a remote sandbox (via subprocess-e2b instead of subprocess-local), and bash, PTY, and LSP all move to that sandbox simultaneously, with no code changes in any consumer.
This is the deeper meaning of “capability seam” — it’s not just pluggability, it’s coordinated pluggability. Related capabilities share underlying providers, so a single infrastructure swap cascades through the entire tool surface.
The Filesystem Seam: Four Providers, Many Consumers
The filesystem seam (ctx.fs) demonstrates the pattern at scale:
| Package | Role | Notes |
|---|---|---|
dsh-fs | Definition | Defines ctx.fs with read/write/edit/stat/glob operations |
dsh-fs-local | Provider | Local filesystem access |
dsh-fs-e2b | Provider | E2B sandbox filesystem |
dsh-fs-sandbox | Provider | Sandboxed/restricted filesystem |
dsh-tool-fs | Consumer | Model tools: edit, read, read_image, write |
dsh-tool-fs-search | Consumer | Model tools: glob, grep |
dsh-fs-observation-policy | Policy | Read-before-write enforcement via fs/* events |
The filesystem also demonstrates event-based policy injection. dsh-fs-observation-policy doesn’t implement the filesystem — it listens to fs/* events and enforces that the model must read a file before editing it. This policy works regardless of which provider backs ctx.fs.
The Complete Seam Inventory
DSH ships with 30+ capability seams. Here are the major families:
| Seam | ctx Key | Definition | Providers | Consumers |
|---|---|---|---|---|
| LLM | ctx.llm | dsh-llm | llm-deepseek, llm-pi-ai, llm-replay | agent-loop, compaction-basic |
| Shell/Bash | ctx.shell | dsh-shell | bash-local, bash-sandbox, pwsh-local | tool-bash, tool-pwsh |
| Subprocess | ctx.subprocess | dsh-subprocess | subprocess-local, subprocess-e2b | bash-local, lsp-stdio, terminal-bash, subagent-acp |
| Filesystem | ctx.fs | dsh-fs | fs-local, fs-e2b, fs-sandbox | tool-fs, tool-fs-search |
| Sandbox | ctx.sandbox | dsh-sandbox | sandbox-local | fs-sandbox, bash-sandbox |
| Session Persistence | ctx.sessionPersistence | dsh-session-persistence | persistence-jsonl, persistence-sqlite | session-persistence |
| Web | ctx.web | dsh-web | web-search-exa, web-search-perplexity, web-search-deepseek, web-fetch-http | tool-web |
| LSP | ctx.lsp | dsh-lsp | lsp-stdio, lsp-local | tool-lsp |
| Compaction | ctx.compaction | dsh-compaction | compaction-basic | command-compact |
| Subagents | ctx.subagents | dsh-subagent | subagent-spawn-in-process, subagent-fork-in-process, subagent-acp, subagent-codex, subagent-claude-code, subagent-dsh-sdk | tool-subagent, tool-subagent-control |
| Terminals | ctx.terminals | dsh-terminal | terminal-bash | tool-terminal |
| Storage | ctx.storage | dsh-storage | storage-json, storage-sqlite | storage-domain |
| Credentials | ctx.credentials | dsh-credentials | credentials-local | Multiple |
| Telemetry | ctx.sessionTelemetry | dsh-session-telemetry | session-telemetry-otel | Multiple |
How It Works in Cordis
The Cordis paper — “A Programming Paradigm for Spatiotemporal Composability” — the theoretical foundation of DSH’s seam pattern.
deepseek-ai/deepseek-harness — 18.5k stars, 12,293 commits, MIT license.
Under the hood, seams use Cordis’s service dependency system:
// Service Definition — declares ctx.shell
export abstract class ShellService extends Service {
constructor(ctx: Context) {
super(ctx, 'shell')
}
abstract execute(request: BashRequest): Promise<BashResult>
}
// Provider — implements ctx.shell
export function apply(ctx: Context) {
ctx.plugin(BashLocalProvider) // extends ShellService
}
// Consumer — uses ctx.shell
export const inject = ['tools', 'shell'] // declare dependency
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'bash',
// ... schema
execute: (args) => ctx.shell.execute(args)
}))
}
The inject declaration tells Cordis: “don’t activate this plugin until ctx.shell exists.” Load order is expressed through service requirements, not manual sequencing. And registrations are reversible effects — if the provider unloads, the consumer’s tool automatically deregisters.
Design Principles Behind Seams
Reading the DSH architecture docs reveals several deliberate principles:
1. No role alone is a seam. A Definition without a Provider and Consumer is just an interface. Adding a capability means designing all three roles.
2. Closed unions for safety. The LSP seam exposes exactly four operations (goToDefinition, findReferences, goToImplementation, hover). Adding a fifth is a compile-enforced change across the seam, all providers, and the tool — you can’t accidentally add an operation only one provider handles.
3. Events for interception, methods for capability. Policy (like “read before write”) attaches via events; direct execution uses service methods. This keeps the method surface small while allowing arbitrary policy injection.
4. Registrations are effects. Every tool schema, prompt section, adapter, and listener is installed through ctx.effect(). When a plugin unloads, everything it registered unwinds automatically. No orphaned state.
5. The seam owns normalization. Consumers never see provider quirks. The web search seam truncates results to maxResults regardless of whether the provider over-returns. The LSP seam normalizes all responses into a closed discriminated union.
What This Means for Plugin Developers
If you’re building a DSH plugin, the seam pattern gives you a decision framework:
- Adding a new capability? Design all three roles. Put them in separate packages if they’ll evolve independently.
- Adding a new provider for an existing capability? Implement the Service Definition’s abstract methods. Your provider competes with existing ones purely through config.
- Adding a new consumer? Inject the Definition (e.g.,
inject: ['shell']). You’ll never import a concrete provider. - Adding policy? Listen to the seam’s events. You don’t need to wrap the provider or modify the consumer.
Comparison: How Others Do It
| DSH (Capability Seams) | LangChain | OpenClaw | Claude Managed Agents | |
|---|---|---|---|---|
| Extensibility model | Three-role seam, config swap | Runnable interface chain | Fork + adapter | Not extensible |
| Provider swap cost | One config line | Refactor consumer code | Fork pipeline | N/A |
| Coordinated swap | Yes (subprocess moves bash+LSP+PTY) | No | No | N/A |
| Compile-time safety | Closed unions, inject declarations | Runtime duck typing | Runtime | N/A |
| Reversible registration | Built-in (Cordis effects) | Manual cleanup | Manual | N/A |
The unique contribution isn’t “plugins” — it’s coordinated infrastructure swap through shared capability ownership. That’s what other frameworks can’t do without forking.
For more on DSH’s architecture, see our DeepSeek Harness developer preview overview and the three-way framework comparison.
FAQ
What happens if no provider is loaded for a seam?
Consumers that inject the service simply don’t activate. Their tools never register, their prompt sections never appear. The system degrades gracefully — no crashes, just missing capabilities.
Can I run multiple providers for the same seam?
Some seams support multiple providers (like ctx.web with separate search and fetch providers). Others are single-provider by design (like ctx.shell). The Definition’s interface determines this.
How do I know which seams exist?
Run dsh --profile web --dump-config to see your active plugin tree. The capability-seams doc has a full mermaid graph of all service relationships.
Is this pattern unique to DSH?
The three-role separation exists in other systems (e.g., driver models in operating systems). What’s distinctive is combining it with Cordis’s reversible effects, typed event system, and config-driven composition — making the swap a user-facing config change rather than a code change.
What about performance overhead?
Minimal. The indirection is one ctx.shell.execute() call rather than a direct function call. The real cost is Cordis’s dependency resolution at boot time, which is a one-time O(n) pass over the plugin graph.


