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.

💡 Dica: O valor central do modelo de capacidade de três papéis é "substituibilidade" — substitua um Provider e você substitui todo o comportamento do produto, enquanto Definition e Consumer permanecem inalterados. Esse é o poder da seam.

📋 Pré-requisitos: Ter completado 19-service.md, entender a classe base Service

1. O Que Você Vai Aprender

Modelo de Três Papéis de Capacidade


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:

TYPESCRIPT
// ❌ 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:

TYPESCRIPT
// ✅ 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

100%
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":

TYPESCRIPT
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:

(4) Convenção

Definitions são tipicamente colocadas em um arquivo separado dos Providers:

TEXT 📖 Somente leitura
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:

TYPESCRIPT
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:

TYPESCRIPT
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:

TYPESCRIPT
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:

TYPESCRIPT
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:

TYPESCRIPT
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:

TYPESCRIPT
// Código Consumer é idêntico independentemente de Local ou Sandbox abaixo
const size = await ctx['file-stats'].getSize(path)

(3) Extensão de Tipo

TYPESCRIPT
// 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":

100%
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

TEXT 📖 Somente leitura
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

TEXT 📖 Somente leitura
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

100%
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

100%
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

100%
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:

TEXT 📖 Somente leitura
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

YAML
# 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

TYPESCRIPT
// Alternar dinamicamente via $replace
ctx.on('config/updated', (config) => {
  if (config.environment === 'sandbox') {
    // Framework auto-recarrega, alterna para Sandbox Provider
  }
})

(4) Teste A/B

YAML
# 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

P Definition deve ser uma interface?
R Sim. Definition descreve uma interface pura (apenas assinaturas de métodos, sem implementação). Se precisar de código compartilhado, coloque em um módulo utilitário separado.
P Uma Definition pode ter múltiplos Providers ativos?
R Não por padrão — registros posteriores sobrescrevem anteriores. Se coexistência é necessária, use isolamento de realm.
P Há interrupção quando um Provider é substituído?
R Brevemente. Entre o descarregamento do Provider antigo e carregamento do novo, a capacidade fica temporariamente indisponível. Consumers devem tratar casos undefined.
P Como decidir se uma funcionalidade deve ser abstraída como capacidade?
R Pergunte-se: esta funcionalidade pode ter implementações diferentes no futuro? Se sim, abstraia; se definitivamente apenas uma implementação, use Service diretamente.
P Qual a diferença entre seam e inject?
R inject é o mecanismo de declaração de dependência ("preciso de X"); seam é design de substituibilidade ("X pode ser substituído"). inject é pré-requisito para seam — Consumer declara dependência via inject, seam garante que a dependência é substituível.
P O sistema de capacidade vale a complexidade adicionada?
R Para projetos simples, talvez não. Mas quando um projeto tem múltiplos ambientes de implantação (local/sandbox/remoto) ou precisa de teste A/B, os benefícios do sistema de capacidade superam em muito seus custos.

📖 Resumo


📝 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%