DeepSeek Harness: Capability Three Roles
Last updated: 2026-08-31
Cordis's capability system is the foundation of "everything is a plugin." It splits a feature into three roles: the one who defines the interface, the one who implements it, and the one who uses it. This separation makes replacing an implementation as easy as swapping batteries — pull out the old one, plug in the new one, and the system runs normally.
📋 Prerequisites: Completed 19-service.md, understand the Service base class
1. What You'll Learn
- Definition: declaring interfaces
- Provider: implementing interfaces
- Consumer: using interfaces
- seam concept
- Capability registration graph
- Replacing a Provider = replacing entire product behavior
2. Capability System Overview
(1) Why Three Roles
Without a capability system, features are directly hardcoded:
// ❌ Hardcoded implementation
class FileAnalyzer {
analyze(path: string) {
const stat = fs.statSync(path) // Can only use local filesystem
return { size: stat.size, type: 'local' }
}
}
With a capability system, features are split into three layers:
// ✅ Three-role separation
// Definition: declare interface
interface FileStats {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
}
// Provider: implementation (local)
class LocalFileStats implements FileStats { ... }
// Provider: implementation (remote sandbox)
class SandboxFileStats implements FileStats { ... }
// Consumer: usage (doesn't care about specific implementation)
class FileAnalyzer {
constructor(private stats: FileStats) {}
analyze(path: string) {
const size = await this.stats.getSize(path)
return { size }
}
}
(2) ▶ Example 2
graph LR
DEF[Definition<br/>Declare interface] --> PROV[Provider<br/>Implement interface]
PROV --> CON[Consumer<br/>Use interface]
CON --> DEF
(3) Analogy
| Role | Analogy | Real-world counterpart |
|---|---|---|
| Definition | Power outlet standard | National outlet specification |
| Provider | Outlet implementation | Wall outlet |
| Consumer | Electrical device | TV, refrigerator |
A TV doesn't care which power plant the electricity comes from — it only cares that the outlet meets the standard. Similarly, a Consumer doesn't care who the Provider is — it only cares about the interface defined by the Definition.
3. Definition: Declaring Interfaces
(1) Defining a Capability
Definition declares the capability's interface — it contains no implementation, only describes "what this capability can do":
import { defineCapability } from '@deepseek-ai/cordis'
export const FileStats = defineCapability({
name: 'file-stats',
description: 'File statistics and metadata access',
interface: {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
exists(path: string): Promise<boolean>
list(dir: string): Promise<string[]>
}
})
(2) Definition Elements
| Element | Description |
|---|---|
name |
Capability identifier, globally unique |
description |
Capability description |
interface |
TypeScript interface definition |
(3) Why Separate Definitions
Benefits of separating Definition from Provider:
- Type constraints: Provider must implement all interface methods
- Documentation value: Definition is the capability's usage documentation
- Replaceability: Any new Provider satisfying the interface can replace the old one
- Compile-time checking: TypeScript ensures interface consistency
(4) Convention
Definitions are typically placed in a separate file from Providers:
capabilities/
├── file-stats/
│ ├── definition.ts ← Definition
│ ├── local.ts ← Provider (local implementation)
│ └── sandbox.ts ← Provider (sandbox implementation)
4. Provider: Implementing Interfaces
(1) Implementing a Capability
Provider implements the interface declared by Definition:
import { FileStats } from './definition'
export default class LocalFileStatsProvider extends Service {
static inject = ['fs']
constructor(ctx: Context) {
super(ctx, 'file-stats')
ctx.implement(FileStats, {
async getSize(path: string) {
const stat = await ctx.fs.stat(path)
return stat.size
},
async getType(path: string) {
const stat = await ctx.fs.stat(path)
return stat.isDirectory ? 'directory' : 'file'
},
async exists(path: string) {
try {
await ctx.fs.stat(path)
return true
} catch {
return false
}
},
async list(dir: string) {
const entries = await ctx.fs.readdir(dir)
return entries.map(e => e.name)
}
})
}
}
(2) ctx.implement()
ctx.implement(capability, implementation) registers the implementation to the capability:
ctx.implement(FileStats, {
getSize: async (path) => { ... },
getType: async (path) => { ... },
// Must implement all interface methods
})
If methods are missing, TypeScript reports a compile-time error.
(3) ▶ Example 3
Local Provider:
export default class LocalFileStatsProvider extends Service {
constructor(ctx: Context) {
super(ctx, 'file-stats')
ctx.implement(FileStats, {
async getSize(path) {
const stat = await ctx.fs.stat(path)
return stat.size
},
async getType(path) {
return (await ctx.fs.stat(path)).isDirectory ? 'directory' : 'file'
},
async exists(path) {
try { await ctx.fs.stat(path); return true } catch { return false }
},
async list(dir) {
return (await ctx.fs.readdir(dir)).map(e => e.name)
}
})
}
}
Remote Sandbox Provider:
export default class SandboxFileStatsProvider extends Service {
constructor(ctx: Context) {
super(ctx, 'file-stats')
ctx.implement(FileStats, {
async getSize(path) {
const resp = await fetch(`http://sandbox:8080/stat?path=${path}`)
return (await resp.json()).size
},
async getType(path) {
const resp = await fetch(`http://sandbox:8080/stat?path=${path}`)
return (await resp.json()).type
},
async exists(path) {
const resp = await fetch(`http://sandbox:8080/exists?path=${path}`)
return (await resp.json()).exists
},
async list(dir) {
const resp = await fetch(`http://sandbox:8080/ls?dir=${dir}`)
return (await resp.json()).entries
}
})
}
}
5. Consumer: Using Interfaces
(1) Consuming a Capability
Consumer declares dependencies via inject and uses the capability through ctx:
export const inject = ['file-stats']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'file_info',
description: 'Get file information',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path' }
},
required: ['path']
},
async execute({ path }, ctx) {
const size = await ctx['file-stats'].getSize(path)
const type = await ctx['file-stats'].getType(path)
return { path, size, type }
}
}))
}
(2) Consumer Doesn't Know the Provider
Consumer only depends on the interface defined by Definition, not the specific implementation:
// Consumer code is identical regardless of Local or Sandbox underneath
const size = await ctx['file-stats'].getSize(path)
(3) ▶ Example 3
// Via type extension
declare module '@deepseek-ai/cordis' {
interface Context {
'file-stats': {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
exists(path: string): Promise<boolean>
list(dir: string): Promise<string[]>
}
}
}
6. seam Concept
(1) What is a seam
A seam is the joint point between Definition and Provider — it's where the system can be "unplugged and replaced":
graph LR
CON[Consumer] -->|depends on| DEF[Definition<br/>seam]
DEF -->|implements| PROV_A[Provider A<br/>Local impl]
DEF -.->|replace with| PROV_B[Provider B<br/>Sandbox impl]
(2) Value of seam
Without seam:
Consumer → Provider A (hardcoded, cannot replace)
With seam:
Consumer → Definition (seam) → Provider A
(seam) → Provider B (replaced!)
seam makes the system replaceable at every capability point.
(3) Identifying Good seams
| Criterion | Good seam | Bad seam |
|---|---|---|
| Abstraction level | Just right | Too fine or too coarse |
| Implementation count | Could have multiple | Only one possible |
| Change frequency | Implementation may change | Implementation never changes |
| Dependency direction | Consumer depends on interface | Consumer depends on implementation |
(4) seam Granularity
Coarse seam: FileSystem (entire filesystem replaceable)
Medium seam: FileStats (file statistics replaceable)
Fine seam: FileSize (file size query replaceable)
Too coarse → high replacement cost; too fine → interface fragmentation. Choose medium granularity.
7. Capability Registration Graph
(1) Registration Flow
graph TB
DEF[defineCapability<br/>Declare interface] --> REG[Register Definition]
REG --> PROV1[Provider A implement]
REG --> PROV2[Provider B implement]
PROV1 --> ACTIVE_A[Currently active: A]
PROV2 --> WAIT_B[Waiting: B]
ACTIVE_A --> CONSUMER[Consumer uses]
(2) Replacement Flow
graph LR
OLD[Provider A<br/>Currently active] -->|unload| INACTIVE_A[Deactivated]
NEW[Provider B<br/>New registration] -->|implement| ACTIVE_B[Currently active]
ACTIVE_B --> CONSUMER[Consumer<br/>Auto-switches]
(3) Multi-Capability Collaboration
graph TB
FS_DEF[FileStats Definition] --> FS_PROV[FileStats Provider]
DB_DEF[Database Definition] --> DB_PROV[Database Provider]
FS_PROV --> TOOL[file_info Tool]
DB_PROV --> TOOL
TOOL --> AGENT[Agent]
8. Replacing a Provider = Replacing Entire Product Behavior
(1) Core Value
This is the capability system's most powerful feature:
Scenario: Switch from local development to sandbox execution
1. Unload LocalFileStatsProvider
2. Load SandboxFileStatsProvider
3. All Consumers automatically use the sandbox implementation
4. Consumer code: zero modifications
(2) Configuration Switching
# Local development
plugins:
file-stats:
$insert: ./providers/local-file-stats
# Sandbox environment (only change this one line)
plugins:
file-stats:
$replace: ./providers/sandbox-file-stats
(3) Runtime Switching
// Switch dynamically via $replace
ctx.on('config/updated', (config) => {
if (config.environment === 'sandbox') {
// Framework auto-reloads, switches to Sandbox Provider
}
})
(4) A/B Testing
# Group A: local implementation
realms:
group-a:
plugins:
file-stats:
$insert: ./providers/local-file-stats
group-b:
plugins:
file-stats:
$insert: ./providers/sandbox-file-stats
❓ FAQ
undefined cases.📖 Summary
- Capability three roles: Definition (declare interface), Provider (implement interface), Consumer (use interface)
- seam is the joint between Definition and Provider, the key to system replaceability
- After Provider replacement, Consumers automatically use the new implementation with zero code changes
- Good seams choose medium granularity with appropriate abstraction
- Capability system suits multi-deployment environments, A/B testing, extensible architectures
📝 Exercises
1. ⭐ Basic: Define a TimeService capability (Definition) with a now(): number method. Implement a LocalTimeProvider, register it, and call it from a Consumer plugin.
2. ⭐⭐ Intermediate: Implement a MockTimeProvider (returns a fixed timestamp), use $replace to switch Providers. Verify the Consumer's call results change from real time to fixed time without any Consumer code modifications.
3. ⭐⭐⭐ Challenge: Design a SearchEngine capability, defining a search(query: string): Promise<string[]> interface. Implement two Providers: LocalGrepProvider (searches with grep) and RemoteAPIProvider (calls a search API). Use realm configuration to let two Agents use different search implementations, verifying isolation.