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.
📋 Pré-requisitos: Ter completado 14-inject.md, entender declarações de dependência inject
1. O Que Você Vai Aprender
- Grafo de dependência e ordenação topológica
- Auto-carregamento quando dependências estão prontas
- Mecanismo Hot Module Replacement (HMR)
- Context Aninhado
- Recarregamento acionado por mudança de config
- Melhores práticas de HMR para desenvolvimento
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):
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:
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:
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
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:
// 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
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
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:
[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:
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
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
- Monitor de sistema de arquivos detecta mudança em
src/index.ts - Fiber do plugin antigo entra em estado disposing
- Todas as funções de limpeza executam (ctx.effect, auto-limpeza)
- Novo código compila e carrega
- Novo Fiber criado, entra em pending/active
- 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:
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":
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:
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
Context pai: { tools, llm, sessions }
Context filho: { tools(sobreposto), cache(adicionado) }
Context filho vê: { tools(versão sobreposta), llm, sessions, cache }
- Busca de serviço: verificar context filho primeiro, depois context pai (padrão de cadeia de protótipo)
- Propagação de evento: eventos do context filho borbulham para o context pai
- Limpeza de recursos: destruição do context filho não afeta o context pai
(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:
// ❌ 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:
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:
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:
- Mudanças na lista inject
- Mudanças em número de porta
- Mudanças em parâmetros de registro de serviço
(4) Recarregamento e Persistência
Mudanças de config são persistidas no cordis.yml após recarregamento:
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:
// ✅ 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
// ❌ 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:
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:
# 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
// 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
[hmr] loading xxx (new) e [xxx] plugin reloaded. Falhas exibem mensagens de erro.--watch é apenas para desenvolvimento.📖 Resumo
- Grafo de dependência é construído como DAG a partir de declarações inject; ordenação topológica determina ordem de carregamento
- Satisfação dinâmica de dependência: plugins pendentes se auto-ativam quando dependências ficam prontas
- HMR habilitado via
--watch; alterações de código auto-recarregam plugins - Contextos aninhados herdam serviços do pai, suportando substituições e isolamento
- Mudanças de config acionam recarregamento automático; algumas mudanças podem atualizar a quente
- Melhores práticas HMR: apply idempotente, evitar estado global, funções de limpeza completas
📝 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.