Files
deepseek-harness/docs/user/develop/practice/index.md
T

163 lines
5.1 KiB
Markdown
Raw Normal View History

# Three-layer capability design
2026-07-09 16:07:58 +08:00
English | [中文](index.zh.md)
2026-07-09 16:07:58 +08:00
When a capability is general enough to need replaceable implementations, such as Bash execution, Harness splits it into three packages: an **interface**, an **implementation**, and a **consumer**. Each layer can evolve or be replaced independently.
2026-07-09 16:07:58 +08:00
## Bash example
2026-07-09 16:07:58 +08:00
The Bash execution capability consists of:
- **Interface** (`dsh-bash`) — defines Bash request and result shapes
- **Implementation** (`dsh-bash-local`) — executes commands on the local machine
- **Consumer** (`dsh-tool-bash`) — exposes the capability as a model-callable tool
2026-07-09 16:07:58 +08:00
```
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ dsh-bash │────▶│ dsh-bash-local │ │ dsh-tool-bash│
│ (interface) │ │ (implementation) │ │(consumer/tool)│
2026-07-09 16:07:58 +08:00
└─────────────┘ └──────────────────┘ └──────────────┘
▲ │
└────────────────────────────────────────────┘
inject: ['bash']
```
## Benefits of the split
2026-07-09 16:07:58 +08:00
### Replace implementations
2026-07-09 16:07:58 +08:00
One interface can have multiple implementations selected through `cordis.yml`:
2026-07-09 16:07:58 +08:00
```yaml
# Local execution
2026-07-09 16:07:58 +08:00
- name: '@deepseek-ai/dsh-bash-local'
# Or a future remote sandbox implementation
2026-07-09 16:07:58 +08:00
# - name: '@deepseek-ai/dsh-bash-remote'
# config:
# endpoint: 'https://sandbox.example.com'
```
The interface and tool remain unchanged while the implementation changes.
2026-07-09 16:07:58 +08:00
### Evolve independently
2026-07-09 16:07:58 +08:00
- The interface changes rarely after its contract stabilizes.
- Implementations can improve performance and security independently.
- Consumers can change how they present the capability to the model.
2026-07-09 16:07:58 +08:00
### Decouple dependencies
2026-07-09 16:07:58 +08:00
- The implementation depends on the interface.
- The consumer depends on the interface.
- The implementation and consumer **do not depend on each other**.
2026-07-09 16:07:58 +08:00
## Built-in three-layer capabilities
2026-07-09 16:07:58 +08:00
| Capability | Interface | Implementation | Consumer |
2026-07-09 16:07:58 +08:00
|------|-------------|------|---------------|
| Bash | `dsh-bash` | `dsh-bash-local` | `dsh-tool-bash` |
| Filesystem | `dsh-fs` | `dsh-fs-local` + `dsh-fs-policy` | `dsh-tool-fs` |
2026-07-09 16:07:58 +08:00
| Web | `dsh-web` | `dsh-web-fetch-local` / `dsh-web-search-*` | `dsh-tool-web` |
| Subagent | `dsh-subagent` | `dsh-subagent-spawn` / `dsh-subagent-fork` | `dsh-tool-subagent` |
| Compaction | `dsh-compact` | `dsh-compact-basic` | The implementation consumes agent-loop extension events |
2026-07-09 16:07:58 +08:00
## Develop a three-layer capability
2026-07-09 16:07:58 +08:00
### Step 1: define the interface
2026-07-09 16:07:58 +08:00
```ts ignore-check
2026-07-09 16:07:58 +08:00
// packages/my-cap/my-cap/src/index.ts
import { Service, type Context } from 'cordis'
declare module 'cordis' {
interface Context {
myCap: MyCapService
}
}
export abstract class MyCapService extends Service {
constructor(ctx: Context) {
super(ctx, 'myCap')
}
/** Execute the capability. */
2026-07-09 16:07:58 +08:00
abstract execute(request: MyCapRequest): Promise<MyCapResult>
}
export interface MyCapRequest {
input: string
}
export interface MyCapResult {
output: string
}
```
### Step 2: write an implementation
2026-07-09 16:07:58 +08:00
```ts ignore-check
2026-07-09 16:07:58 +08:00
// packages/my-cap/my-cap-local/src/index.ts
import type { Context } from 'cordis'
import { MyCapService, type MyCapRequest, type MyCapResult } from '@deepseek-ai/dsh-my-cap'
class MyCapLocal extends MyCapService {
async execute(request: MyCapRequest): Promise<MyCapResult> {
// Concrete implementation.
2026-07-09 16:07:58 +08:00
return { output: request.input.toUpperCase() }
}
}
export const name = 'my-cap-local'
export function apply(ctx: Context) {
ctx.plugin(MyCapLocal)
}
```
### Step 3: write a consumer
2026-07-09 16:07:58 +08:00
```ts ignore-check
2026-07-09 16:07:58 +08:00
// packages/my-cap/tool-my-cap/src/index.ts
import type { Context } from 'cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'tool-my-cap'
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 },
},
2026-07-21 03:08:35 +08:00
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
2026-07-09 16:07:58 +08:00
async execute(args) {
const result = await ctx.myCap.execute({ input: args.input })
2026-07-21 03:08:35 +08:00
return result.output
2026-07-09 16:07:58 +08:00
},
}))
}
```
### Compose them in cordis.yml
2026-07-09 16:07:58 +08:00
```yaml
- name: '@deepseek-ai/dsh-my-cap-local'
- name: '@deepseek-ai/dsh-tool-my-cap'
```
## Design points
2026-07-09 16:07:58 +08:00
- **Do not split preemptively** — use three packages only when the capability needs replaceable implementations. A simple tool plugin does not.
- **The interface owns Request/Result types** — implementations and consumers depend only on the interface package.
- **Explicit > implicit** — resolve defaults in an explicit `resolve(request): Spec` step rather than hiding `?? default` expressions inside `run()`.
2026-07-09 16:07:58 +08:00
## Next steps
2026-07-09 16:07:58 +08:00
- [LLM adapter](./llm-adapter.md) — implement an LLM backend, a common capability interface extension