DeepSeek Harness: Três Papéis da Capacidade
Última atualização: 2026-08-31
O sistema de capacidade do Cordis é a fundação de "tudo é um plugin." Ele divide uma funcionalidade em três papéis: quem define a interface, quem a implementa, e quem a usa. Esta separação faz com que substituir uma implementação seja tão fácil quanto trocar pilhas — retire a velha, coloque a nova, e o sistema funciona normalmente.
📋 Pré-requisitos: Ter completado 19-service.md, entender a classe base Service
1. O Que Você Vai Aprender
- Definition: declarando interfaces
- Provider: implementando interfaces
- Consumer: usando interfaces
- Conceito seam
- Grafo de registro de capacidade
- Substituir um Provider = substituir todo o comportamento do produto
2. Visão Geral do Sistema de Capacidade
(1) Por Que Três Papéis
Sem um sistema de capacidade, funcionalidades são diretamente hardcoded:
// ❌ Implementação hardcoded
class FileAnalyzer {
analyze(path: string) {
const stat = fs.statSync(path) // Só pode usar sistema de arquivos local
return { size: stat.size, type: 'local' }
}
}
Com um sistema de capacidade, funcionalidades são divididas em três camadas:
// ✅ Separação de três papéis
// Definition: declarar interface
interface FileStats {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
}
// Provider: implementação (local)
class LocalFileStats implements FileStats { ... }
// Provider: implementação (sandbox remoto)
class SandboxFileStats implements FileStats { ... }
// Consumer: uso (não se importa com implementação específica)
class FileAnalyzer {
constructor(private stats: FileStats) {}
analyze(path: string) {
const size = await this.stats.getSize(path)
return { size }
}
}
(2) Diagrama de Três Papéis
graph LR
DEF[Definition<br/>Declarar interface] --> PROV[Provider<br/>Implementar interface]
PROV --> CON[Consumer<br/>Usar interface]
CON --> DEF
(3) Analogia
| Papel | Analogia | Contraparte real |
|---|---|---|
| Definition | Padrão de tomada | Especificação nacional de tomada |
| Provider | Implementação da tomada | Tomada na parede |
| Consumer | Aparelho elétrico | TV, geladeira |
Uma TV não se importa de qual usina a eletricidade vem — ela só se importa que a tomada atende ao padrão. Similarmente, um Consumer não se importa quem é o Provider — ele só se importa com a interface definida pela Definition.
3. Definition: Declarando Interfaces
(1) Definindo uma Capacidade
▶ Exemplo 1: Definindo a Capacidade FileStats
Definition declara a interface da capacidade — não contém implementação, apenas descreve "o que esta capacidade pode fazer":
import { defineCapability } from '@deepseek-ai/cordis'
export const FileStats = defineCapability({
name: 'file-stats',
description: 'Estatísticas de arquivo e acesso a metadados',
interface: {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
exists(path: string): Promise<boolean>
list(dir: string): Promise<string[]>
}
})
(2) Elementos da Definition
| Elemento | Descrição |
|---|---|
name |
Identificador da capacidade, globalmente único |
description |
Descrição da capacidade |
interface |
Definição de interface TypeScript |
(3) Por Que Separar Definitions
Benefícios de separar Definition de Provider:
- Restrição de tipo: Provider deve implementar todos os métodos da interface
- Valor documental: Definition é a documentação de uso da capacidade
- Substituibilidade: Qualquer novo Provider satisfazendo a interface pode substituir o antigo
- Verificação em tempo de compilação: TypeScript garante consistência da interface
(4) Convenção
Definitions são tipicamente colocadas em um arquivo separado dos Providers:
capabilities/
├── file-stats/
│ ├── definition.ts ← Definition
│ ├── local.ts ← Provider (implementação local)
│ └── sandbox.ts ← Provider (implementação sandbox)
4. Provider: Implementando Interfaces
(1) Implementando uma Capacidade
▶ Exemplo 2: Provider Local para FileStats
Provider implementa a interface declarada pela 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) registra a implementação na capacidade:
ctx.implement(FileStats, {
getSize: async (path) => { ... },
getType: async (path) => { ... },
// Deve implementar todos os métodos da interface
})
Se métodos estão ausentes, TypeScript reporta erro em tempo de compilação.
(3) Dois Providers
Provider Local:
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)
}
})
}
}
Provider Sandbox Remoto:
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: Usando Interfaces
(1) Consumindo uma Capacidade
▶ Exemplo 3: Consumindo a Capacidade FileStats em uma Ferramenta
Consumer declara dependências via inject e usa a capacidade através de 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 Não Conhece o Provider
Consumer depende apenas da interface definida pela Definition, não da implementação específica:
// Código Consumer é idêntico independentemente de Local ou Sandbox abaixo
const size = await ctx['file-stats'].getSize(path)
(3) Extensão de Tipo
// Via extensão de tipo
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. Conceito seam
(1) O Que É uma seam
Uma seam é o ponto de junção entre Definition e Provider — é onde o sistema pode ser "desplugado e substituído":
graph LR
CON[Consumer] -->|depende de| DEF[Definition<br/>seam]
DEF -->|implementa| PROV_A[Provider A<br/>Impl local]
DEF -.->|substituir por| PROV_B[Provider B<br/>Impl sandbox]
(2) Valor da seam
Sem seam:
Consumer → Provider A (hardcoded, não pode substituir)
Com seam:
Consumer → Definition (seam) → Provider A
(seam) → Provider B (substituído!)
seam torna o sistema substituível em cada ponto de capacidade.
(3) Identificando Boas seams
| Critério | Boa seam | Má seam |
|---|---|---|
| Nível de abstração | Adequado | Muito fino ou muito grosso |
| Contagem de implementações | Poderia ter múltiplas | Apenas uma possível |
| Frequência de mudança | Implementação pode mudar | Implementação nunca muda |
| Direção de dependência | Consumer depende da interface | Consumer depende da implementação |
(4) Granularidade da seam
seam grossa: FileSystem (sistema de arquivos inteiro substituível)
seam média: FileStats (estatísticas de arquivo substituíveis)
seam fina: FileSize (consulta de tamanho de arquivo substituível)
Grossa demais → alto custo de substituição; fina demais → fragmentação de interface. Escolha granularidade média.
7. Grafo de Registro de Capacidade
(1) Fluxo de Registro
graph TB
DEF[defineCapability<br/>Declarar interface] --> REG[Registrar Definition]
REG --> PROV1[Provider A implement]
REG --> PROV2[Provider B implement]
PROV1 --> ACTIVE_A[Ativo atualmente: A]
PROV2 --> WAIT_B[Aguardando: B]
ACTIVE_A --> CONSUMER[Consumer usa]
(2) Fluxo de Substituição
graph LR
OLD[Provider A<br/>Ativo atualmente] -->|descarregar| INACTIVE_A[Desativado]
NEW[Provider B<br/>Novo registro] -->|implement| ACTIVE_B[Ativo atualmente]
ACTIVE_B --> CONSUMER[Consumer<br/>Auto-alterna]
(3) Colaboração Multi-Capacidade
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. Substituir um Provider = Substituir Todo o Comportamento do Produto
(1) Valor Central
Esta é a funcionalidade mais poderosa do sistema de capacidade:
Cenário: Alternar de desenvolvimento local para execução em sandbox
1. Descarregar LocalFileStatsProvider
2. Carregar SandboxFileStatsProvider
3. Todos os Consumers automaticamente usam a implementação sandbox
4. Código Consumer: zero modificações
(2) Alternância por Configuração
# Desenvolvimento local
plugins:
file-stats:
$insert: ./providers/local-file-stats
# Ambiente sandbox (apenas mudar esta linha)
plugins:
file-stats:
$replace: ./providers/sandbox-file-stats
(3) Alternância em Runtime
// Alternar dinamicamente via $replace
ctx.on('config/updated', (config) => {
if (config.environment === 'sandbox') {
// Framework auto-recarrega, alterna para Sandbox Provider
}
})
(4) Teste A/B
# Grupo A: implementação local
realms:
group-a:
plugins:
file-stats:
$insert: ./providers/local-file-stats
group-b:
plugins:
file-stats:
$insert: ./providers/sandbox-file-stats
❓ Perguntas Frequentes
undefined.📖 Resumo
- Capacidade três papéis: Definition (declarar interface), Provider (implementar interface), Consumer (usar interface)
- seam é a junção entre Definition e Provider, a chave para substituibilidade do sistema
- Após substituição do Provider, Consumers automaticamente usam a nova implementação sem mudanças de código
- Boas seams escolhem granularidade média com abstração apropriada
- Sistema de capacidade é adequado para múltiplos ambientes de implantação, teste A/B, arquiteturas extensíveis
📝 Exercícios
1. ⭐ Básico: Defina uma capacidade TimeService (Definition) com método now(): number. Implemente um LocalTimeProvider, registre-o, e chame-o de um plugin Consumer.
2. ⭐⭐ Intermediário: Implemente um MockTimeProvider (retorna timestamp fixo), use $replace para alternar Providers. Verifique que os resultados de chamada do Consumer mudam de tempo real para tempo fixo sem nenhuma modificação de código Consumer.
3. ⭐⭐⭐ Desafio: Projete uma capacidade SearchEngine, definindo interface search(query: string): Promise<string[]>. Implemente dois Providers: LocalGrepProvider (busca com grep) e RemoteAPIProvider (chama API de busca). Use configuração realm para que dois Agents usem implementações de busca diferentes, verificando isolamento.