DeepSeek Harness: Carregamento Orientado por Dependência e…

Última atualização: 2026-08-31

De "controlar manualmente a ordem de carregamento" a "declarar dependências, o framework cuida do resto" — carregamento orientado por dependência significa que desenvolvedores apenas declaram relações. Combinado com Hot Module Replacement (HMR), alterações de código entram em vigor sem reinicialização. A experiência de desenvolvimento melhora dramaticamente.

💡 Dica: Dependência orientada + HMR = "altere e entra em vigor." Você apenas declara relações de dependência; o framework trata da ordem de carregamento. Você apenas salva código; o framework trata do hot reload. O ganho central de produtividade é eliminar o tempo "aguardando reinicialização."

📋 Pré-requisitos: Ter completado 14-inject.md, entender declarações de dependência inject

1. O Que Você Vai Aprender

Carregamento e Recarga Orientados por Dependência


2. Grafo de Dependência e Ordenação Topológica

(1) Construindo o Grafo de Dependência

▶ Exemplo 1: Construção do Grafo de Dependência

Na inicialização, o framework percorre as declarações inject de todos os plugins e constrói um Grafo Acíclico Direcionado (DAG):

TYPESCRIPT
function buildDependencyGraph(plugins: Plugin[]) {
  const graph = new DAG()
  for (const plugin of plugins) {
    graph.addNode(plugin.name)
    for (const dep of plugin.inject) {
      graph.addEdge(dep, plugin.name)
    }
  }
  return graph
}

(2) Ordenação Topológica

A ordenação topológica determina a ordem de carregamento:

TEXT 📖 Somente leitura
Declarações de plugins:
  core:    inject = []
  tools:   inject = ['core']
  llm:     inject = ['core']
  my-tool: inject = ['tools', 'llm']

Grafo de dependência:
  core → tools → my-tool
  core → llm   → my-tool

Resultado da ordenação topológica: [core, tools, llm, my-tool]

(3) Carregamento Paralelo

Plugins sem relações de dependência carregam em paralelo:

100%
graph TB
    subgraph Fase1[Fase 1]
        CORE[core]
    end
    subgraph Fase2[Fase 2 - Paralelo]
        TOOLS[tools]
        LLM[llm]
    end
    subgraph Fase3[Fase 3]
        MYTOOL[my-tool]
    end
    CORE --> TOOLS
    CORE --> LLM
    TOOLS --> MYTOOL
    LLM --> MYTOOL

(4) Grafo de Dependência Complexo

100%
graph TB
    CORE[core] --> TOOLS[tools]
    CORE --> SESSIONS[sessions]
    CORE --> LOGGER[logger]
    TOOLS --> MY_TOOL[my-tool]
    SESSIONS --> MY_TOOL
    LOGGER --> TRAJECTORY[trajectory]
    SESSIONS --> TRAJECTORY

3. Auto-Carregamento Quando Dependências Estão Prontas

(1) Satisfação Dinâmica de Dependências

Plugins não precisam de todas as dependências satisfeitas na inicialização. Quando um serviço de dependência é registrado posteriormente, plugins pendentes se ativam automaticamente:

TYPESCRIPT
// Plugin A: inject = ['tools'] — tools ainda não registrado
// → Estado Fiber: pending

// Depois, tools plugin carrega e registra serviço
// → Fiber do Plugin A auto-transiciona para active, chama apply

(2) Registro Condicional de Serviço

TYPESCRIPT
export function apply(ctx: Context) {
  if (someCondition) {
    ctx.provide('optional-service', impl)
    // Plugins pendentes dependendo de optional-service agora se auto-ativam
  }
}

(3) Diagrama de Sequência de Ativação

100%
sequenceDiagram
    participant F as Framework
    participant A as Plugin A (inject: tools)
    participant T as Tools Plugin
    
    F->>A: Registrar → pending (tools não pronto)
    F->>T: Registrar → active
    T->>F: Registrar serviço tools
    F->>A: Dependência pronta → active
    A->>F: apply() executa

(4) Dependências Permanentemente Insatisfatíveis

Se uma dependência obrigatória declarada nunca pode ser satisfeita:

TEXT 📖 Somente leitura
[warn] plugin my-plugin tem dependência insatisfeita: nonexistent-service
[warn] my-plugin permanecerá em estado pending

O plugin não entra em erro — apenas permanece em pending para sempre. Dependências opcionais (com ?) não produzem aviso quando insatisfeitas.


4. Mecanismo Hot Module Replacement (HMR)

(1) Conceito HMR

Hot Module Replacement permite substituir código de plugin em runtime sem reiniciar todo o DSH:

100%
graph LR
    CHANGE[Alteração de código] --> DETECT[Detecção de mudança de arquivo]
    DETECT --> DISPOSE[Fiber antigo disposing]
    DISPOSE --> LOAD[Novo código carregado]
    LOAD --> ACTIVE[Novo Fiber active]

(2) Habilitando HMR

▶ Exemplo 2: Habilitando Hot Reload

BASH
pnpm dsh web --patch --watch

A flag --watch habilita monitoramento de arquivos; quando o código-fonte do plugin muda, ele automaticamente aciona um recarregamento.

(3) Fluxo HMR Completo

  1. Monitor de sistema de arquivos detecta mudança em src/index.ts
  2. Fiber do plugin antigo entra em estado disposing
  3. Todas as funções de limpeza executam (ctx.effect, auto-limpeza)
  4. Novo código compila e carrega
  5. Novo Fiber criado, entra em pending/active
  6. Plugins dependentes recarregam conforme necessário

(4) Limitações do HMR

Cenário Suporte HMR Observações
Modificar função execute Lógica da ferramenta hot-atualiza
Modificar Config Configuração re-validada
Modificar inject ⚠️ Pode acionar recarregamento em cascata
Modificar name Requer reinicialização manual
Modificar versões de dependência Requer reinicialização manual

(5) Recarregamento em Cascata

Quando um plugin do qual se depende recarrega, plugins que dependem dele também recarregam:

TEXT 📖 Somente leitura
tools plugin HMR reload → my-tool (depende de tools) também recarrega

Isso garante consistência de dependência, mas pode causar "tempestades de recarregamento":

TEXT 📖 Somente leitura
core reload → tools reload → my-tool reload → ... (toda a cadeia de dependência recarrega)

5. Context Aninhado

(1) Hierarquia de Context

Cordis suporta contextos aninhados — contextos filhos herdam serviços do contexto pai mas podem sobrepô-los:

TYPESCRIPT
export function apply(ctx: Context) {
  const childCtx = ctx.extend({
    // Sobrepor ou adicionar serviços
  })
  
  childCtx.plugin({
    name: 'child-plugin',
    apply(innerCtx) {
      // innerCtx herda serviços de ctx
    }
  })
}

(2) Regras de Herança de Context

TEXT 📖 Somente leitura
Context pai: { tools, llm, sessions }
Context filho: { tools(sobreposto), cache(adicionado) }

Context filho vê: { tools(versão sobreposta), llm, sessions, cache }

(3) Casos de Uso de Context Aninhado

Cenário Descrição
Isolamento de sessão Cada sessão tem um ctx independente
Escopo de requisição Cada requisição cria um ctx temporário
Testes Criar contextos de teste isolados
Multi-Agent Cada Agent tem um conjunto independente de ferramentas

(4) Profundidade de Aninhamento

Teoricamente ilimitada, mas aninhamento excessivo impacta desempenho:

TYPESCRIPT
// ❌ Profundo demais
ctx.extend().extend().extend().extend()

// ✅ Aninhamento moderado
const sessionCtx = ctx.extend({ session })

6. Recarregamento Acionado por Mudança de Config

(1) Recarregamento Automático

Quando usuários modificam configuração de plugin na Web UI, o framework automaticamente aciona um recarregamento:

100%
graph LR
    UI[Web UI modifica config] --> VALID[Validação Schema]
    VALID --> OLD[Fiber antigo disposing]
    OLD --> NEW[Nova Config + Novo Fiber]
    NEW --> ACTIVE[Fiber active]

(2) Atualização a Quente Parcial de Config

Algumas mudanças de config não requerem recarregamento completo:

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.on('config/updated', (newConfig) => {
    if (newConfig.debug !== ctx.config.debug) {
      ctx.logger.level = newConfig.debug ? 'debug' : 'info'
    }
  })
}

(3) Configs Que Requerem Recarregamento Completo

Estas mudanças de config requerem recarregamento completo:

(4) Recarregamento e Persistência

Mudanças de config são persistidas no cordis.yml após recarregamento:

YAML
plugins:
  my-plugin:
    config:
      debug: true  # Modificado pelo usuário via Web UI, auto-persistido

7. Melhores Práticas de HMR para Desenvolvimento

(1) Mantenha apply Idempotente

▶ Exemplo 3: Apply Idempotente vs Não Idempotente

A função apply deve ser idempotente — múltiplas chamadas produzem resultados consistentes:

TYPESCRIPT
// ✅ Idempotente: cada apply registra a mesma ferramenta
export function apply(ctx: Context) {
  ctx.tools.register(fileCountTool)
}

// ❌ Não idempotente: apply acumula efeitos colaterais
let counter = 0
export function apply(ctx: Context) {
  counter++  // Contador incrementa após reload
}

(2) Evite Estado Global

TYPESCRIPT
// ❌ Estado global: estado antigo persiste após reload HMR
const globalCache = new Map()

// ✅ Estado de closure: cada apply cria novo estado
export function apply(ctx: Context) {
  const cache = new Map()
  ctx.effect(() => () => cache.clear())
}

(3) Funções de Limpeza Completas

Durante reload HMR, as funções de limpeza do Fiber antigo devem limpar completamente todos os recursos:

TYPESCRIPT
export function apply(ctx: Context) {
  const ws = new WebSocket('ws://localhost:8080')
  
  // ✅ Registrar limpeza
  ctx.effect(() => () => ws.close())
  
  // ❌ Esqueceu limpeza → conexão antiga vaza após reload
}

(4) Fluxo de Trabalho de Desenvolvimento

Loop de desenvolvimento HMR recomendado de Alice:

BASH
# 1. Iniciar modo de desenvolvimento com HMR
pnpm dsh web --patch --watch

# 2. Escrever código normalmente, auto-recarrega ao salvar
# Saída do terminal:
# [hmr] file changed: src/index.ts
# [hmr] disposing my-plugin (old)
# [hmr] loading my-plugin (new)
# [my-plugin] plugin reloaded

# 3. Verificar logs de reload, confirmar sem erros de limpeza

(5) Dicas de Depuração HMR

TYPESCRIPT
// Adicionar log de depuração no início de apply
export function apply(ctx: Context) {
  ctx.logger.info('apply chamado em', new Date().toISOString())
  // ...
}
// Se ver apply chamado inesperadamente múltiplas vezes, significa reload HMR em cascata

❓ Perguntas Frequentes

P Qual a diferença entre HMR e reinicialização manual?
R HMR apenas recarrega plugins alterados e suas cadeias de dependência; outros plugins não são afetados. Reinicialização manual recarrega todos os plugins, levando mais tempo.
P Sessões são perdidas durante reload HMR?
R Não. Dados de sessão são gerenciados pelo serviço sessions, que não é afetado pelo HMR. Apenas o estado dos plugins recarregados é perdido.
P Como saber se um recarregamento foi bem-sucedido?
R Verifique os logs do terminal. Recarregamentos bem-sucedidos exibem [hmr] loading xxx (new) e [xxx] plugin reloaded. Falhas exibem mensagens de erro.
P E se recarregamentos em cascata são muito frequentes?
R Verifique se está dependendo desnecessariamente de plugins de baixo nível (como core). Se um plugin de ferramenta depende apenas de tools, ele não entrará em cascata quando core recarregar.
P Comportamento HMR em contextos aninhados?
R Quando um plugin em um context filho recarrega, apenas plugins dentro desse context filho são afetados; o context pai não é afetado.
P Devo usar HMR em produção?
R Não. HMR é uma ferramenta de desenvolvimento; produção deve usar carregamento estável. A flag --watch é apenas para desenvolvimento.

📖 Resumo


📝 Exercícios

1. ⭐ Básico: Inicie pnpm dsh web --patch --watch, modifique a função apply de um plugin carregado (adicione uma linha de log), salve e observe as mensagens de reload HMR no terminal.

2. ⭐⭐ Intermediário: Crie dois plugins com relação de dependência (A inject B). Inicie HMR, modifique o código de B, e observe se A recarrega em cascata. Depois modifique apenas o código de A e confirme que B não é afetado.

3. ⭐⭐⭐ Desafio: Escreva um plugin usando variáveis globais (não idempotente). Após reload HMR, observe mudanças no valor da variável. Depois refatore para estado de closure (idempotente) e verifique comportamento consistente após reload. Registre a comparação da saída do terminal antes e depois da refatoração.

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%