Um agent harness multi-provedor — e a plataforma de orquestração autônoma construída sobre ele — para desenvolvimento de software.
O DEILE tem duas faces que compartilham o mesmo núcleo Python:
- 🧑💻 CLI interativo — você conversa em linguagem natural no terminal e ele lê, escreve e edita arquivos, roda comandos, executa testes, busca no repositório, planeja tarefas e acompanha custo — tudo no diretório de trabalho atual.
- ☸️ Pipeline autônomo em Kubernetes — uma frota de pods que monitora um repositório GitHub ou GitLab, refina issues, implementa em branches
auto/issue-N, abre PRs/MRs e revisa-as sem intervenção humana.
No núcleo, o DEILE é um agent harness — o runtime que transforma uma API de LLM em agente de verdade: loop de tool-calling, roteamento com fallback, streaming, memória e permissões. É a mesma categoria de Claude Code, aider ou Codex CLI — só que sobre 4 provedores em vez de um.
Em volta desse harness há uma plataforma de orquestração autônoma (o pipeline + a frota K8s) que coordena agentes em paralelo sobre um forge. E aqui está o detalhe que define a categoria: o DEILE pode dirigir outro harness — o pod claude-worker executa claude -p (o próprio Claude Code) como um dos workers despacháveis por etapa do pipeline, lado a lado com o deile-worker (o motor próprio do DEILE). Ou seja: harness no núcleo, meta-harness/orquestrador na borda.
Este README descreve apenas o que existe no código. Cada afirmação foi conferida na fonte. Onde algo é experimental, parcial ou um gap conhecido, está sinalizado.
| Seção | O quê |
|---|---|
| 🚀 Visão geral | O que o DEILE faz, para quem |
| ⚡ Quick start | Subir o CLI em minutos |
| 🧰 Ferramentas | As tools que o agente aciona |
| 📊 Comandos slash | O REPL e suas flags CLI |
| 🌐 Provedores & modelos | LLMs, tiers, fallback |
| 🧠 Reasoning effort | Esforço de raciocínio por etapa |
| 🎭 Personas · 🧩 Skills · 🧠 Memória | Componentes plugáveis |
| 🧩 Sub-DEILEs paralelos | Decomposição concorrente |
| 🤖 Pipeline autônomo | A máquina de estados de issue→PR |
| 🌳 Forge GitHub + GitLab | Forge-agnóstico |
| ☸️ Stack Kubernetes | Os 6 pods, deploy.py, painel |
| 🔭 Observabilidade | OTLP, runtime state, status servers |
| 🔒 Segurança · 💾 Persistência | Permissões, custo, SQLite |
| 🏗️ Arquitetura · ⚙️ Configuração | Camadas e settings |
| 📋 Requisitos · 🧪 Testes · 🚦 Operação | Engenharia |
| Realidade & contribuição |
DEILE pensa, decide e resolve: aciona ferramentas reais (function calling) para entender o problema, planejar e concluir o que foi pedido, mostra o que está fazendo em tempo real (streaming) e mantém memória da conversa entre turnos. Funciona em português ou inglês.
| Perfil | O que ganha |
|---|---|
| 👩💻 Pessoa desenvolvedora | Um par de programação que executa, não só sugere |
| 🛠️ Engenharia de plataforma | Automação de tarefas repetitivas dentro do repositório |
| 🔍 Revisão de código | Leitura guiada do projeto em linguagem natural |
| 🤖 Operação autônoma | Frota que processa issues → PRs/MRs 24/7 num cluster |
| 🎓 Aprendizado | Observar passo a passo como um agente decide e usa ferramentas |
| Capacidade | Descrição |
|---|---|
| 💬 Conversa multi-turno | Contexto e histórico de sessão persistentes |
| 🖼️ Streaming UI | Resposta em streaming com renderização incremental de Markdown no terminal |
| 🔁 Loop de ferramentas | Function calling iterativo até concluir a tarefa (limite configurável) |
| 🧩 Sub-DEILEs paralelos | Decompõe pedidos complexos em N sub-agentes em paralelo, com painel ao vivo |
| 🛠️ Edição de código | Lê, cria, edita, deleta e busca arquivos no repositório |
| ⚙️ Execução local | Shell/Python, instala pacotes, roda testes |
| 🌐 Roteamento LLM | Roteia entre 4 provedores com fallback e seleção por tier |
| 🧠 Reasoning effort | Esforço de raciocínio ajustável (low…ultracode), global e por etapa do pipeline |
| 🧠 Memória | Quatro camadas: working, episodic, semantic, procedural |
| 📋 Orquestração | Planeja tarefas, gerencia dependências, workflows com rollback |
| ⌨️ Comandos slash | ~40 comandos no REPL; vários também expostos como flag CLI |
| 🎭 Personas | Comportamento via Markdown + YAML, sem mudar Python |
| 🧩 Skills | Unidades de expertise em Markdown com hot-reload e 4 gatilhos de auto-injeção |
| 🤖 Pipeline autônomo | Issue → refino → implementação → PR → review → merge, em GitHub ou GitLab |
| 💰 Telemetria | Tokens, latência e custo em USD com persistência SQLite + ledger durável |
| 🔭 Observabilidade | OpenTelemetry (traces/métricas) + runtime state por processo + painel TUI |
| 🔒 Segurança | Permissões, aprovação por risco, auditoria tipada e scanner de segredos |
Pré-requisitos: Python 3.9+ e ao menos uma chave de API entre Anthropic, OpenAI, DeepSeek e Gemini.
🧭 Cobertura por chave:
OPENAI_API_KEYouDEEPSEEK_API_KEYcobrem todas as tiers (1–4).GOOGLE_API_KEYcobre tiers 1–3.ANTHROPIC_API_KEYcobre tiers 1–3. Para cobertura plena e fallback entre provedores, use pelo menos duas chaves.
git clone https://github.com/elimarcavalli/deile.git
cd deileO próprio deile.py faz o setup na 1ª execução: cria .venv, pergunta as chaves de API (input oculto), gera o .env, instala dependências e sobe a CLI. Nas execuções seguintes, detecta o ambiente e inicia direto.
python3 deile.pypython3 -m venv .venv
source .venv/bin/activate # macOS/Linux (.venv\Scripts\activate no Windows)
pip install -r requirements.txt
cp .env.example .env # ~540 linhas, seções comentadas
# preencha ao menos uma chave: ANTHROPIC_API_KEY / OPENAI_API_KEY / DEEPSEEK_API_KEY / GOOGLE_API_KEY / OPENROUTER_API_KEY
python3 deile.pyUse /help para listar os comandos. Para uma única mensagem (one-shot), passe o prompt como argumento.
⚠️ Compatibilidade: homologado para Unix-like (Linux/macOS). Windows pode funcionar, mas é experimental (o status server por Unix-socket e o flock viram no-op).
python3 deile.py --install # pergunta o modo (global/local) e instala
python3 deile.py --install --install-mode global # venv isolado em ~/.deile/venv/ (pula o prompt)
python3 deile.py --install --install-mode local # venv no próprio repo (<repo>/.venv/)O --install cria um venv isolado (não toca o Python do sistema — contorna o PEP 668 sem --break-system-packages), instala o DEILE editável lá dentro e cria um shim deile em ~/.local/bin/ (tentando ajustar o PATH no rc do shell). Depois:
deile # REPL interativo
deile "resuma a arquitetura do repo" # one-shot
deile --version # versão
deile --status # painel de saúde (não exige API key)
deile --tools # lista tools registradas
deile --model-list # tabela de modelos
deile --pipeline-status # status do pipeline autônomo
deile --help # TODAS as flags + catálogo de slash commandsVários comandos slash têm uma flag CLI correspondente, gerada automaticamente a partir do
CommandRegistry(issue #126) — declararcli_flag = "--foo"na classe do comando cria a flag (nem todos os ~40 comandos expõem uma).
O LLM aciona ferramentas reais via function calling. O conjunto-padrão é auto-descoberto pelo ToolRegistry (deile/tools/discovery.py); demais tools são registradas explicitamente.
| Tool | Função |
|---|---|
read_file |
Ler arquivo (encoding, limites de tamanho) |
write_file |
Escrever arquivo de forma atômica |
edit_file |
Edição cirúrgica (substituição de trecho) |
delete_file |
Remover arquivo com política |
list_files |
Listar arquivos em diretório |
find_in_files |
Buscar padrões na árvore do projeto |
| Tool | Função |
|---|---|
bash_execute |
Executar comando shell (com níveis de segurança próprios) |
python_execute |
Executar bloco/arquivo Python |
pip_install |
Instalar dependência Python |
run_tests |
Rodar a suíte de testes |
vision_describe_image |
Descrever/analisar uma imagem |
| Tool | Função |
|---|---|
dispatch_parallel_subagents |
Decompõe o pedido em 2–5 sub-DEILEs paralelos (sessões limpas) |
dispatch_deile_task |
Despacha uma tarefa a outro DEILE (worker) |
worktree |
Cria/gerencia git worktrees isolados |
pipeline |
Controla o pipeline autônomo (start/stop/status) |
pipeline_schedule |
Agenda ações do pipeline (recorrente/one-shot, cron) |
cron_create · cron_list · cron_delete |
Agenda prompts naturais via cron genérico |
| Tool | Função |
|---|---|
list_skills |
Catálogo machine-readable das skills carregadas |
invoke_skill |
Carrega o body de uma skill por nome (sob demanda) |
remember_preference · list_preferences · forget_preference |
Memória de preferências do usuário |
discord_send_message, discord_send_dm, discord_edit_message, discord_react, discord_pin_message, discord_start_thread, discord_mention_role, discord_get_user_profile, whatsapp_send_template.
🔒 Tools de DM e menção de cargo são
SecurityLevel.DANGEROUSe passam peloApprovalSystempor design. Elas só se registram quandoimport deilebotfunciona eDEILE_BOT_ENDPOINT+DEILE_BOT_AUTH_TOKENestão configurados.
Todos em deile/commands/builtin/. Aliases entre parênteses.
| Grupo | Comandos |
|---|---|
| 🧭 Sessão | /help · /status · /welcome · /context · /compact · /clear (/cls) · /stop · /rename · /resume · /rewind (/rw) · /fork |
| 💰 Custo & métricas | /cost · /loc (/estatisticas) · /standup |
| 🧠 Config & runtime | /config · /settings · /env · /model · /reasoning (/effort) · /memory · /permissions · /debug |
| 🛠️ Trabalho | /tools · /skills · /plan · /run · /todo · /diff · /patch (/patch-generate) · /apply (/patch-apply) · /approve · /logs · /export |
| 🤖 Pipeline & cluster | /pipeline · /pipeline-schedule · /backlog · /pods · /panel |
| ℹ️ Outros | /sandbox · /version (/ver) |
Além dos built-ins, toda skill carregada vira
/<name>automaticamente (exceto as bundled emdeile/skills/library/, que ficam só via auto-trigger einvoke_skill). Skills de~/.claude/commands/registram em UPPERCASE.
Definição em deile/config/model_providers.yaml. Provedores só são registrados quando a respectiva chave de ambiente está setada (bootstrap_providers() em deile/core/models/bootstrap.py).
| Provider | Classe | Chave de ambiente | SDK / endpoint |
|---|---|---|---|
| Anthropic | AnthropicProvider |
ANTHROPIC_API_KEY |
anthropic |
| OpenAI | OpenAIProvider |
OPENAI_API_KEY |
openai |
| DeepSeek | DeepSeekProvider ⟵ estende OpenAIProvider |
DEEPSEEK_API_KEY |
openai (endpoint api.deepseek.com/v1) |
| Gemini | GeminiProvider |
GOOGLE_API_KEY |
google-genai |
| OpenRouter | OpenRouterProvider ⟵ estende OpenAIProvider |
OPENROUTER_API_KEY |
openai (endpoint openrouter.ai/api/v1) |
🌐 OpenRouter (Decisão #50) é um gateway OpenAI-compatible: uma chave dá acesso a todos os modelos (slug
openrouter:vendor/model, com/no model_id). O custo é autoritativo — vem do campousage.costda própria resposta (extra_body={"usage":{"include":true}}), com a tabela do YAML só como fallback (sem undercount).⚠️ Privacidade: o prompt é roteado a provedores terceiros (hop documentado no YAML).
ModelRouter(router.py) — roteador legado baseado em estratégia.TierRouter(tier_router.py) — roteamento por tier (tier_1complexo →tier_4bulk/barato), com um circuit breaker por provider injetado (CircuitBreaker, estados emBreakerState) e fallback automático na cascata do tier.- Estratégias (
routing_strategies.py):task_optimized(default) ecost_optimized, além deround_robin/least_busy. - Budget guard (
BudgetGuardemdeile/storage/usage_repository.py) — enforcement de orçamento por sessão e por provider (diário e mensal).
| Provider | Modelos (tier) |
|---|---|
| Anthropic | claude-opus-4-8 (1) · claude-sonnet-4-6 (2) · claude-haiku-4-5 (3) |
| OpenAI | gpt-5.5 (1) · gpt-5.4 (2) · gpt-5.4-mini (3) · gpt-5.4-nano (4) |
| Gemini | gemini-3.1-pro-preview (1) · gemini-2.5-pro (1) · gemini-3.5-flash (2) · gemini-3-flash-preview (2) · gemini-3.1-flash-lite · gemini-2.5-flash · gemini-2.5-flash-lite |
| DeepSeek | deepseek-v4-pro (1) · deepseek-v4-flash (3) |
Cada entrada do YAML declara pricing (input/output/cached por 1M tokens) e
context_window— base para a telemetria de custo. O nome do modelo precisa ser válido no SDK do provider.
Fonte única em deile/core/models/reasoning.py. O DEILE adota o vocabulário do Claude Code — low · medium · high · xhigh · max · ultracode · auto — e o traduz para o parâmetro nativo de cada provider (Anthropic effort, OpenAI reasoning_effort, Gemini thinking_config, DeepSeek). A tradução é fail-open: nunca quebra o turno.
Configurável em três níveis: global (DEILE_REASONING_EFFORT / /reasoning no CLI), por etapa do pipeline (DEILE_PIPELINE_REASONING_<STAGE>) e na coluna Reasoning do painel TUI.
🔒 Hard override por sessão (/reasoning use <nível>): grava forced_reasoning_effort no contexto da sessão e vence a injeção per-turn do worker, estabelecendo a precedência forced > session > global > provider default. /reasoning use clear remove o override (idempotente); /reasoning use auto força o default do provider (não é clear). O slot soft (/reasoning <nível>) segue inalterado.
Personas são MD-driven: editar o Markdown muda o comportamento, sem tocar em Python.
- 📚 Library (
deile/personas/library/*.yaml):analyst,architect,debugger,developer,reviewer(nome/id/capacidades). - 📝 Instruções (
deile/personas/instructions/*.md):analyst,architect,debugger,developer,reviewer,discord_developer,monitor,monitor_qa,fallback(a prosa que entra no system prompt).
O pipeline escolhe a persona pelo tipo da issue/etapa — analyst refina intents, architect arquiteta features/refactors, debugger investiga bugs e reviewer revisa PRs. A persona monitor fica fora desse despacho por tipo: supervisiona o namespace K8s na Fase B do pod deile-monitor (o tick mecânico do pipeline é determinístico, sem LLM).
Skills são unidades composáveis de expertise em Markdown puro (sem código Python). O loader varre as fontes abaixo em ordem de prioridade crescente — em colisão de nome o source mais alto vence (INFO log):
| Origem | Caminho | Comportamento |
|---|---|---|
| Bundled | deile/skills/library/**/*.md |
Vai no pacote; PR no repo. Bundled out-of-the-box: python, typescript, tdd |
| Usuário pessoal | ~/.deile/skills/*.md |
Visível em qualquer projeto seu |
| Usuário (Claude compat) | ~/.claude/commands/*.md |
Nome registrado em UPPERCASE (kind=command) |
| Projeto | <cwd>/.deile/skills/*.md |
Versionada no git, viaja com o repo |
| Projeto (Claude compat) | <cwd>/.claude/commands/*.md |
UPPERCASE (kind=command) |
| Configurada | library_paths: em deile/config/skills.yaml ou /skills add |
Paths extras (escopo global/project) |
- Auto-injeção no system prompt quando um
triggercasa (atémax_per_turn=4, ordenadas por(-priority, name)):file_globs—fnmatchno basename/pathcode_block_langs— fence```pythonno input (case-insensitive)keywords— match literal (escapado) com word-boundary, case-insensitive (não confunde "rust" com "trust")file_content_patterns— regex em 4 KiB de cada arquivo referenciado, contido aoproject_root(segurança)
- Function-calls
invoke_skill(name)elist_skills— o LLM puxa skills que não dispararam por trigger. - Slash
/<name>— invocação explícita pelo usuário, com argumentos opcionais.
🔁 Hot-reload via watchdog: dropar/editar/remover um .md reflete em ~0,5 s, sem restart (swap atômico no SkillRegistry).
Quatro camadas em deile/memory/, agregadas via MemoryManager (memory_manager.py). A regra: estado cross-turn não vive em globals de módulo — vai para a camada certa.
| Camada | Módulo | Propósito |
|---|---|---|
| Working | working_memory.py |
Estado transitório do turno (TTL) |
| Episodic | episodic_memory.py |
Eventos da sessão — persistido em SQLite (aiosqlite, episodes.db) |
| Semantic | semantic_memory.py |
Fatos/conhecimento de longa duração |
| Procedural | procedural_memory.py |
Padrões/skills aprendidos |
Nenhuma camada guarda segredos ou PII; toda escrita é assíncrona. A consolidação entre camadas fica em
memory_consolidation.py.
Numa conversa interativa, o DEILE pode identificar sub-tarefas independentes e substanciais e dispará-las em paralelo — cada uma num sub-DEILE com sessão limpa (contexto/histórico próprios). Você vê o progresso ao vivo num painel multipanel.
✅ "Refator módulo A E módulo B (não-acoplados)" · "Gere testes pra X, Y e Z" ❌ Tarefas sequenciais · micro-tarefas (<30s) · mesmo arquivo
O LLM chama dispatch_parallel_subagents com 2–5 sub-tarefas. O SubAgentOrchestrator dispara em paralelo (semaphore DEILE_SUBAGENT_MAX_PARALLEL, default 3), respeitando o budget global. Cada sub-DEILE vai direto ao tool-loop, então o painel mostra ⚙ bash_execute(...), ✓ write_file: 412 bytes, ✎ texto-em-curso em tempo real.
🧩 Decomposto em 2 frentes paralelas · 1 ok · 1/2 concluídas · 00:08
╭─ ▶ sub-DEILE #1 · refatorar auth.py ──────────────────────────── 00:08 ──╮
│ ⚙ bash_execute(pytest deile/tests/auth -q) │
│ ✓ bash_execute: 5 passed in 0.04s │
│ ✎ aplicando guard clauses (3/5 funções) │
╰──────────────────────────────────────────────────────────────────────────╯
╭─ ✅ sub-DEILE #2 · doc do módulo X ──────────────────────────────────────╮
│ ✅ concluído · docs/x.md │
╰──────────────────────────────────────────────────────────────────────────╯
(toque 1-9 para focar · ESC: fecha painel)
Runners pluggable: LocalSubAgentRunner (default, in-process via asyncio) ou WorkerSubAgentRunner (delega ao deile-worker por HTTP — DEILE_SUBAGENT_RUNNER=worker). Falha de uma frente não cancela as siblings; o resumo final é gravado no histórico e o /resume reconstrói o painel.
| Variável | Default | Uso |
|---|---|---|
DEILE_SUBAGENT_RUNNER |
local |
local | worker |
DEILE_SUBAGENT_MAX_PARALLEL |
3 |
Teto de concorrência por chamada |
DEILE_SUBAGENT_BUDGET_S |
600 |
Teto global de tempo (s) |
DEILE_SUBAGENT_POLL_INTERVAL_S |
0.8 |
Polling do worker runner |
Quando uma issue recebe ~workflow:nova (ou o bot é atribuído/mencionado), o pipeline a leva sozinho até a PR/MR — refinando o escopo antes de escrever uma linha de código. Roda no pod deile-pipeline (PipelineMonitor.tick() em loop).
🆕 ~workflow:nova
│
▼ Stage 1 — crítica de escopo (persona por tipo: analyst/architect/debugger)
🔍 ~workflow:em_revisao
│
├─ VEREDITO: CLARO ──────────────► ✅ ~workflow:revisada
│ │
│ ┌───────────┴────────────┐
│ (code-type) (intent)
│ ▼ ▼
│ 🚀 em_implementacao 🧩 decomposta
│ ▼ (architect abre N derivadas)
│ 📬 em_pr → PR/MR aberta
│
└─ VEREDITO: VAGO ─► 🏷️ refinar + estado de refino:
🧠 em_refinamento (intent → analyst)
🏛️ em_arquitetura (feature/bug/refactor → architect/debugger)
│
↕ ⏸️ aguardando_stakeholder (humano remove p/ retomar)
│
volta p/ 🆕 ~nova (até 5 voltas; estourou → ⛔ bloqueada)
- Portão de refinamento — toda issue nova é criticada por escopo por uma persona escolhida pelo tipo:
intent → analyst,feature/refactor→architect,bug → debugger. Issues VAGAS ganhamrefinar+ estado de refino e são reescritas (corpo, comentários, bracket[TIPO]do título) até 5 voltas (contador durável via label~refine:N). Um gap de alto impacto pode pausar em~workflow:aguardando_stakeholdercom 2–3 opções sugeridas — o humano remove a label para retomar. - Decomposição — intents que passam são quebrados em issues derivadas pelo
architect(default anti-flood: agregar numa única issue com checklist- [ ]; só dividir com independência provada). - PRs/MRs —
~review:pendente→~review:em_andamento→~review:concluida. - Estado terminal & GC —
~workflow:concluidaé aplicada automaticamente pelo GC (run_terminal_gc) a issues fechadas (idempotente, nunca manual — issue #587). - Locks & marcadores —
~batch:<sha8>(claim distribuído, só quando há mais de um monitor);~by:<id>(dono do claim);~mention:processado(idempotência de menção);~follow_ups:processed(idempotência do estágio de follow-ups num PR mergeado);~workflow:bloqueada(bloqueio duro, exclui do auto-resume). - Contadores duráveis (sobrevivem a restart) —
~refine:N(voltas do portão de refinamento, teto 5);~attempt:N(re-claims do reaper sobre trabalho stuck — emN ≥ reaper_max_attempts, default 3, bloqueia em vez de liberar);~prioridade:0..3(reordena a seleção de candidatos — menor primeiro; sem label = por último; PRs herdam da issue vinculada).
classify · refine · implement · pr_review · follow_ups. Para cada etapa você escolhe três eixos independentes (com fallback global e persistência por env var ou ~/.deile/settings.json):
| Eixo | Por etapa | Global | Onde |
|---|---|---|---|
| 👷 Worker | DEILE_PIPELINE_DISPATCH_<STAGE> |
DEILE_PIPELINE_DISPATCH_MODE (built-in default deile-worker) |
dispatch_resolver.py |
| 🧠 Modelo | DEILE_PIPELINE_MODEL_<STAGE> |
DEILE_PREFERRED_MODEL |
model_resolver.py |
| 🤔 Reasoning | DEILE_PIPELINE_REASONING_<STAGE> |
DEILE_REASONING_EFFORT |
reasoning_resolver.py |
📌 O default built-in do resolver é
deile-worker, mas o manifest do pipeline (46-deile-pipeline-deployment.yaml) agora versionaDEILE_PIPELINE_DISPATCH_MODE=claude(all-claude-worker) +DEILE_PIPELINE_MAX_PARALLEL=2como default em produção — antes esse routing vinha dekubectl set enve qualquerkubectl applyo resetava (causando drift/CrashLoop).max_parallel=2alinha ao cap de concorrência do claude-worker (acima disso só gera409+ churn de label).
Cada etapa aponta para um worker da frota:
- 🐍
deile-worker(:8766) — roda o DEILE Python in-process, usando seus próprios provedores de LLM. - 🤖
claude-worker(:8767) — roda oclaude -p(Claude Code CLI) em worktrees isolados sob PVC; auth viaCLAUDE_CODE_OAUTH_TOKEN(token de ~1 ano gerado porclaude setup-token, injetado por env var a partir de Secret — issue #603, semcredentials.json/initContainer). - 🧩 Frota de CLI workers plugáveis — além dos dois núcleos, qualquer CLI de codificação vira um worker despachável escrevendo um adapter (
infra/k8s/cli_adapters/<kind>.py, que satisfaz o ProtocolCliAdapter); o auto-discovery montaADAPTERScomo fonte única que dirige resolver, painel e geração de manifest/NetworkPolicy — nenhum consumidor é editado (ver Decisão #51). Cada worker é um pod com Service próprio (portas derivadas dodefault_portdo adapter), nascereplicas:0(scale-to-zero) e é escalado 0→1 sob demanda (cli_worker_scaler.py). Server genéricoinfra/k8s/cli_worker_server.pyreusa_worker_core.py(lease/heartbeat/subprocess/gate de commit+push+test). Resume nativo no mesmo workdir (sem re-gastar tokens) quando o adapter declarasupports_resume; o custo de cada sessão é empurrado centralmente para oUsageRepository(SQLite central) pelo pipeline via o blocousageda resposta do/v1/dispatch(issue #638 — sobrevive ao scale-to-zero), com fallback no ledger durável por-PVC; a tela[T]okensdo painel lê o store central primeiro.
| Trigger | Ação |
|---|---|
| Issue + assignee/menção no corpo | Injeta ~workflow:nova → o pipeline assume |
| Qualquer trigger sobre uma PR/MR | Brief unificado pr_unified: o worker abre a PR, descobre o estado real (papel autor/assignee/reviewer; HEAD vs último review; threads abertas; comentários dirigidos a mim) e age — revisa, comenta, atende thread ou mergeia. PR open → push direto; merged/closed → branch derivada auto/<orig>-followup-<sha> + nova PR |
| Issue + comentário | Faz o que o comentário pede (one-shot sob persona developer); se a issue está num gate ativo, o próprio gate relê o comentário |
🔒 Quality-gate (adaptativo ao CI, #85): o brief unificado de PR detecta se o repo-alvo tem CI e adapta o portão de regressão — com CI, o revisor exige todos os checks 100% verdes (corrige e dá push até passar) e não roda a suíte in-pod (o CI é o portão); sem CI, ele roda a suíte completa ele mesmo (
python3 -m pytest deile/tests/ -q). Além disso, o estágioreview_one_open_prconsultaforge.get_ci_status(pr)antes de despachar: com o CI aindapending, pula o dispatch neste tick (sem gastar tentativa) e reconcilia no próximo — evitando redispachar a review a cada ~60s enquanto o CI roda por minutos. O implementador roda só os testes impactados (-p no:cov) e delega a suíte/CI ao revisor. O brief confronta entrega vs pedido (claim-vs-código, completude vs ACs, qualidade dos testes, escopo, segurança, design) — verde não substitui requisito faltante.
Despacho fresh é o default; resume só é solicitado (resume=True) quando há trabalho-em-curso real registrado no DispatchLedger (anti-double-dispatch). Sessões claude -p acima do orçamento de tokens (DEILE_CLAUDE_RESUME_TOKEN_BUDGET, ~100K) são promovidas para fresh em vez de rejeitadas. Falhas de auth recorrentes entram em backoff exponencial por target. O brief lê .deile-progress.md no PASSO 0 — então "fresh com contexto natural" cobre a maioria dos casos sem inflar o JSONL da sessão.
export DEILE_FORGE_REPO=owner/repo # env var canônico do repositório (no settings.json: pipeline.repo)
export DEILE_PIPELINE_BASE_PATH=/caminho/para/repo
> /pipeline start # inicia o loop de polling
> /pipeline status # contadores
> /pipeline stop # paraOu DEILE_PIPELINE_AUTOSTART=1. Agendamento via pipeline_schedule(...) (recorrente/one-shot, cron) e cron_create(prompt=..., cron=...).
O DEILE é forge-agnóstico (issue #297). O mesmo agente, pipeline e briefs operam idênticos em GitHub (cloud, GHES) e GitLab (cloud, self-hosted). A camada deile/orchestration/forge/ esconde a diferença sob ForgeClient (ABC); o pipeline nunca chama gh/glab direto.
| Cenário | DEILE_FORGE_KIND |
Hosts | Tokens |
|---|---|---|---|
| 🐙 GitHub cloud (padrão) | auto ou github |
default github.com |
GITHUB_TOKEN (escopos repo + workflow; sem read:org) |
| 🦊 GitLab cloud | gitlab |
default gitlab.com |
GITLAB_TOKEN (escopos api, read_repository, write_repository) |
| 🏢 GitHub Enterprise Server | github |
DEILE_GITHUB_HOST=ghe.empresa.com |
GITHUB_TOKEN do GHES |
| 🏠 GitLab self-hosted | gitlab |
DEILE_GITLAB_HOST=gitlab.empresa.com |
GITLAB_TOKEN do GL |
| 🌐 Multi-forge na sessão CLI | auto |
ambos declarados | AMBOS |
1. DEILE_FORGE_KIND="github"|"gitlab"? ✅ override explícito (vence sempre)
2. host da URL bate github.com/gitlab.com ✅ detecção por URL
ou DEILE_*_HOST declarado? (HTTP probe opt-in via DEILE_FORGE_PROBE=1)
3. project_path com 3+ segmentos → GitLab ✅ heurística de path
2 segmentos (owner/repo) → GitHub (compat retroativa)
GitHubForge opera via gh; GitLabForge via glab + REST v4. O ForgeRouter (singleton) cacheia um cliente por (host, project), permitindo GH e GL na mesma sessão CLI — basta os dois tokens configurados.
| Interno | GitHub | GitLab |
|---|---|---|
| Mudança proposta | PR | MR |
| Comentário | comment |
note |
| Reviewer | requested_reviewers[] |
reviewers[] |
| Numeração | pr.number |
mr.iid |
| URL | /<owner>/<repo>/pull/N |
/<group>/.../<proj>/-/merge_requests/N |
| Templates de issue | .github/ISSUE_TEMPLATE/*.md |
.gitlab/issue_templates/*.md |
| CLI | gh |
glab |
⚠️ Pipeline ≠ CLI: odeile-pipelineautônomo é per-repo, per-forge (uma instância serve um repo). Para GH e GL simultâneos em modo autônomo, rode duas instâncias (cada uma com suaDEILE_FORGE_REPO). A sessão CLI interativa não tem essa restrição.
Garantias verificadas: tokens nunca ficam em /proc/self/environ — o wrapper.py move pra ~/.git-credentials (mode 0600) + config do gh/glab e remove de os.environ antes do agente subir; o secrets_scanner detecta tokens GitHub (ghp_/gho_/ghu_/ghs_/ghr_/github_pat_) e GitLab (glpat-/gldt-/glptt-/glsoat-); back-compat 100% com setups GitHub existentes.
Quem prefere que o agente não toque o filesystem do host (input não-confiável, sandbox descartável, ou operação autônoma 24/7) sobe a stack em infra/k8s/ — testada em Rancher Desktop (k3s/containerd).
Todos os pods compartilham uma única imagem deile-stack:local (imagePullPolicy: Never); /app é baked no build (COPY, não montado) — mudança de código só vai ao ar após rebuild + restart.
| Pod | Porta | Papel |
|---|---|---|
deilebot |
:8765 |
Bridge de I/O (Discord e outros canais) — vive num repo separado |
deile-worker |
:8766 |
Roda DEILE Python in-process; alvo de dispatch HTTP do pipeline |
claude-worker |
:8767 |
Roda claude -p em worktrees isolados; auth via CLAUDE_CODE_OAUTH_TOKEN env (Secret claude-credentials, token ~1 ano — issue #603) |
deile-pipeline |
:8768 (status, in-process) |
O monitor de forge — não recebe dispatch (sem Service de ingestão de tarefas); expõe só o Service read-only deile-pipeline-status p/ o painel. No mais, só "chama pra fora" |
deile-monitor |
:8769 |
Supervisor determinístico do cluster (vigias V1–V8); distinto do pipeline. Expõe um control-plane on-demand (status sem-LLM, ordens, Q&A read-only via persona monitor_qa — o único pod que vê kubectl + forge + /state) consumido pelo deilebot |
deile-shell |
— | Sandbox kubectl exec-only, toolset cheio; prompt vem do humano |
Além dos 6 pods core, qualquer CLI de codificação headless vira um pod-worker despachável. Cada um roda o server genérico cli_worker_server.py (sobre _worker_core.py — lease, heartbeat, subprocess one-shot, gate pós-run de commit+push+test) dirigido por um adapter infra/k8s/cli_adapters/<kind>.py (Protocol CliAdapter). O auto-discovery monta ADAPTERS como fonte única que dirige resolver, painel e geração de manifest/NetworkPolicy — adicionar worker = escrever o adapter, nenhum consumidor é editado (test_worker_registry_drives_everything.py). Todos nascem replicas:0 (scale-to-zero, custo zero ocioso) e são escalados 0→1 sob demanda (cli_worker_scaler.py).
| Worker | Porta | CLI / auth | Resume nativo | Commit |
|---|---|---|---|---|
opencode-worker |
:8771 |
opencode · env (OPENROUTER_API_KEY) |
run --session |
brief_driven |
codex-worker |
:8772 |
codex · env (OPENAI_API_KEY); OAuth opt-in (DEILE_CODEX_AUTH=oauth) |
exec resume |
brief_driven |
qwen-worker |
:8773 |
qwen · env (tríade OpenAI-compat) | --resume |
brief_driven |
aider-worker |
:8774 |
aider · env (OPENROUTER_API_KEY/DEEPSEEK_API_KEY) |
--restore-chat-history |
cli_autocommit (--no-attribute-*) |
goose-worker |
:8775 |
goose · env (OPENROUTER_API_KEY/OPENAI_API_KEY) |
named-session --resume |
brief_driven |
- Success gate, não exit-code:
WorkResult.ok = adapter.parse_output(...).ok ANDo gate pós-run (novo commit + push, ou suíte verde quando o brief exige) — exit-codes de CLI são pouco confiáveis.git_strategy:brief_driven(o brief dirigegit add/commit/push) vscli_autocommit(o aider commita sozinho, sem atribuição). - Resume nativo (anti-sangria de custo, #445): cada worker retoma sua sessão nativa no mesmo workdir em vez de re-gastar tokens; o manifest provisiona PVC
<kind>-worker-home+ CronJob de cleanup. Erro de provider (402/429) é classificado INCOMPLETE para o pipeline retomar. - Custo central (#638): o worker devolve um bloco
usageno/v1/dispatch; o pipeline (long-lived) grava 1 registro noUsageRepository(SQLite central) por modelo — sobrevive ao scale-to-zero, que o ledger por-PVC não fazia (ledger JSONL fica como fallback local). Tela[T]okensdo painel lê o store central primeiro. - Antigravity existe só como gate documentado (
cli_adapters/antigravity.pynão exportaADAPTER→ auto-discovery ignora): closed-source + auth headless inviável; só sai do gate com spike provando Vertex SA. - Buildar/instalar:
deploy.py k8s build-cli-workers [--kind <k>](imagem únicaDockerfile.cli-worker, multi-stage) →cli-worker-install <kind>(auth por env) oucli-worker-login <kind>(adapters OAuth-capable, ex.: codex). Provider de workers OpenAI-compat (qwen) via a convençãoDEILE_CLI_<KIND>_ENV_<VAR>no.env.
deile-monitorroda um tick em duas fases: Fase A é uma varredura mecânica sem LLM (8 vigias: saúde OAuth, pods em erro, issues órfãs, PRsauto/*com tentativa N/3, aguardando-stakeholder, Jobs falhos, saúde do pipeline, coleta de follow-ups); Fase B só aciona a personamonitorquando candidatos sobrevivem à Fase A. Em regime estável, não gasta token. Tick default a cada 30 min. Tem RBAC dedicado (deile-monitor-sa).
Imprime um plano antes de qualquer ação mutante; --yes pula o prompt, --dry-run só mostra o plano. Flag global -n <ns> seleciona o namespace (default deile).
| Objetivo | Comando |
|---|---|
| Menu interativo / lista de verbos | python3 infra/k8s/deploy.py / ... help |
| Rebuild + restart (deploy de código) | python3 infra/k8s/deploy.py k8s build --restart --yes |
| Provisionar/atualizar a stack (idempotente) | python3 infra/k8s/deploy.py k8s up |
| Criar namespace do zero (interativo) | ... k8s create-namespace / ... k8s setup |
| Escalar workers | ... k8s scale --worker 2 --claude-worker 1 |
| Pausar / retomar (scale 0/1; preserva dados) | ... k8s stop / ... k8s start |
| Status / painel TUI / logs | ... k8s status / ... k8s panel / ... k8s logs [bot|worker|pipeline|claude-worker] |
| Bootstrap claude-worker (token ~1 ano) | ... k8s claude-setup-token (preferido — issue #603; renovar ~1×/ano re-rodando o verb) |
| Bootstrap/renovar via OAuth legado (DEPRECATED) | ... k8s claude-login [--switch|--no-interactive] / ... k8s claude-renew |
| Frota CLI: buildar imagem(ns) per-tool | ... k8s build-cli-workers [--kind <k>] (via Dockerfile.cli-worker) |
| Frota CLI: gerar manifest / instalar / login OAuth / remover | ... k8s gen-worker <kind> · ... k8s cli-worker-install <kind> · ... k8s cli-worker-login <kind> [--switch|--no-interactive|--in-pod] · ... k8s cli-worker-uninstall <kind> |
Clonar repo no deile-shell |
... k8s clone <owner/repo> |
| Listar namespaces DEILE | ... k8s list |
| Teardown (apaga o namespace + dados) | ... k8s down |
Multi-stage sobre Python 3.11 (slim). Instala, em camadas verificáveis: gh, glab 1.45.0 (SHA256 conferido), kubectl v1.31.4, procps, tini (reaper de zumbis) e o claude CLI pinado em 2.1.158 (via npm). O DEILE Python e os servidores (wrapper.py, worker_server.py, claude_worker_server.py) são copiados como 0555 (read-only em runtime).
Os pods claude-worker e deile-worker recebem CLAUDE.md/DEILE.md + skills/commands injetados de forma versionada e idempotente, sem instalação em runtime (a NetworkPolicy é default-deny no egress) e sem tocar no código do harness. A fonte canônica vive em infra/k8s/agents/<worker>/; os ConfigMaps claude-worker-agents e deile-worker-agents são derivados dela:
- claude-worker — initContainer
inject-agents(idempotente porcmp -s,set -eu) copia do ConfigMap para o PVC/home/claude/.claude/(CLAUDE.md, skillbrainstormpinada por commit, commandplan.md). - deile-worker — ConfigMap montado read-only em
/etc/deile/agents/(skill nativadeile-systematic-debug);worker-settings.jsonapontadeile_md.user_path/skills_pathspara lá.
runAsNonRoot uid 10001 · capabilities drop ALL · readOnlyRootFilesystem · allowPrivilegeEscalation: false · seccompProfile RuntimeDefault · automountServiceAccountToken: false (exceto os pods que precisam renovar OAuth via kubectl exec) · PSS restricted no namespace · NetworkPolicy default-deny-all + opt-ins explícitos por porta · secrets montados como arquivos em /run/secrets/<role>/ (nunca via env:, então /proc/<pid>/environ fica limpo) · bootstrap_providers() popa as API keys de os.environ após instanciar os providers.
🚫 Honestidade: a "whitelist de egress" do
claude-worker(api.anthropic.com,github.com,gitlab.com, granularidade de repo) é enforçada na aplicação (wrapper.py+ ConfigMapclaude-worker-allowed-repos, fail-closed), não em L3/L4 — a NetworkPolicy libera TCP 443 genérico. É um controle de aplicação, não firewall de rede. A allowlist de repos agora é reverificada por request, antes de qualquer clone, nos dois servidores de dispatch (_worker_core.check_repo_allowed→ 403REPO_NOT_ALLOWED), fechando o gap onde só havia fail-fast no startup (issue #639).
O DEILE roda múltiplas stacks lado a lado, uma por namespace (deile é a default; deile-gl é o piloto GitLab). Os 6 deployments core não fixam namespace — o -n <ns> do deploy.py os aplica em qualquer namespace. Gap conhecido: alguns manifests auxiliares (NetworkPolicy, claude-worker, certos PVCs/CronJobs) ainda fixam namespace: deile — multi-namespace é pleno para o core, parcial para os auxiliares.
deploy.py k8s panel abre um cockpit Rich navegável (não fecha após a escolha): pods (k8s + processos locais), timeline do pipeline, backlog de issues/PRs, feed de atividade e sessões claude -p ao vivo (parser incremental de JSONL). Hotkeys: [1-4] views · [t] auditoria de tokens (suspende e roda session_tokens_audit.py) · [M] monitor · [d] matriz de dispatch (editar por etapa: Worker × Model × Timeout × Retries × Cost-cap × Reasoning; [L] login claude, [I] install, [s] scale, [c] cleanup, [p] max_parallel, [J] retenção JSONL) · [?] ajuda · [q] sair.
deile/events/event_bus.py — EventBus assíncrono com enum EventType: sistema (SYSTEM_STARTED/STOPPED), persona (PERSONA_ACTIVATED/...), tarefas (TASK_CREATED/STARTED/COMPLETED/FAILED/CANCELLED), código (CODE_GENERATED/EXECUTED/TESTED, FILE_MODIFIED) e ferramentas (TOOL_INVOKED/COMPLETED/FAILED). Handlers em deile/events/event_handlers.py.
Tracer + métricas CNCF, com fallback no-op quando o SDK está ausente, DEILE_OTLP_ENDPOINT vazio ou DEILE_OBSERVABILITY_DISABLED=true. Toda chamada é best-effort — nunca quebra o turno.
- Spans:
deile.turn(1 por interação),deile.tool.<name>(1 por execução),deile.llm.call(1 por chamada de provider). Adapter de dispatch: root spandeile.dispatch+ eventosdispatch.*e child spansgit.commit/git.push/forge.pr_open/forge.pr_review. Propagação W3C traceparent pipeline→worker (issue #457): odeile.dispatchvira filho do spanpipeline.dispatch_request; os child spansgit.*/forge.*dual-emitem atributos SemConvvcs.*(vcs.ref.head.name,vcs.repository.url,vcs.change.id/state— issue #456, toggleDEILE_OTLP_SEMCONV_ENABLED, default on). - Métricas: do turno/tool —
deile.tokens.total,deile.cost.usd.total,deile.tool.duration_ms,deile.turn.duration_ms,deile.errors.total; do dispatch autônomo (issue #455,dispatch_metrics.py) —deile.dispatch.total/.failed.total/.duration_ms/.tool_burst.total,deile.forge.pr_review.total,deile.git.push.total(todas com labels de cardinalidade limitada — enum fechado, nuncatask_id/session_id/sha/model). - Sem segredos em atributos — apenas tamanhos, tokens, custo e IDs opacos (redação automática de tokens).
session_iddeliberadamente não é label (controle de cardinalidade). - Env:
DEILE_OTLP_ENDPOINT(vazio = off),DEILE_OTLP_HEADERS,DEILE_OTLP_INSECURE,DEILE_OTLP_SERVICE_NAME,DEILE_OTLP_SAMPLE_RATIO,DEILE_OTLP_SEMCONV_ENABLED,DEILE_OBSERVABILITY_DISABLED.
Cada processo DEILE publica seu estado vivo em ~/.deile/run/<instance_id>.json (escrita atômica + cleanup no atexit), com heartbeat a cada 2 s. O current_action é um enum (idle/starting/tool_execution/llm_call/shutting_down) e o state file acumula tokens/custo/turns/tool_calls/errors — sem segredos, prompts ou tool_args. Um status server por Unix-socket (<id>.sock, chmod 0600) responde STATUS/METRICS/FLUSH em protocolo de linha; um registry.json (lock fcntl.flock, GC de PIDs mortos) dá visão de frota. No Windows, vira no-op.
Não há servidor HTTP público — o agente interativo é puro CLI. Estes endpoints são o control-plane interno do cluster, protegidos por Bearer (exceto
/healthe o fluxo OAuth).
deile-worker (:8766) — GET /v1/health · POST /v1/dispatch · GET /v1/result/{task_id} · GET /v1/progress/{task_id}
claude-worker (:8767) — GET /v1/health · GET /v1/auth/start · GET /v1/auth/status · GET /v1/pod-status · POST /v1/dispatch · GET /v1/progress/{task_id} · GET /v1/dispatches/{task_id}/resume-info · GET /v1/sessions · GET /v1/sessions/{id}/{command,chat,stdout} · POST /v1/sessions/{id}/kill · DELETE /v1/sessions/{id}/cleanup · GET\|POST /v1/cleanup
Frota de CLI workers (cli_worker_server.py, portas derivadas do adapter) — GET /v1/health · GET /v1/models · POST /v1/dispatch · GET /v1/progress/{task_id} · GET /v1/dispatches/{task_id}/resume-info
deile-pipeline status (:8768) — GET /v1/health · GET /v1/pipeline-status[/backlog\|/recent\|/ledger\|/reaper-preview] · POST /v1/pipeline/force-tick
deile-monitor (:8769) — GET /v1/health · GET /v1/monitor-status · POST /v1/command · POST /v1/ask · GET /v1/ask/{request_id}
| Componente | Onde | Papel |
|---|---|---|
| 🛡️ Permissões | deile/security/permissions.py (PermissionManager) |
Verifica permissão antes de ação privilegiada |
| 📜 Audit log | deile/security/audit_logger.py (AuditLogger + AuditEvent tipado) |
Registro tipado de ações sensíveis |
| 🔍 Scanner de segredos | deile/security/secrets_scanner.py |
Detecta/redige credenciais (tokens GitHub e GitLab, entre outros) |
| ✅ Aprovação por risco | deile/orchestration/approval_system.py (ApprovalSystem) |
Gate de ações de alto risco (ex.: DM, menção de cargo) |
Toda tool declara um SecurityLevel (deile/tools/base.py); o bash_tool tem catálogo de risco próprio (assess_risk em deile/tools/_shell_security.py, classificando safe/moderate/dangerous sobre o mesmo enum SecurityLevel). O gate de aprovação interativa das tools de mensageria (DM / menção de cargo) pode ser auto-dispensado para um operador confiável via approval.auto: true em ~/.deile/settings.json (bot_approval_auto).
Os stores relacionais são SQLite auto-criados em runtime (o ledger de custo é JSONL append-only) — não há script SQL versionado;
| Store | Arquivo | Dono |
|---|---|---|
| Tarefas & listas | ./.deile/db/tasks.db (legacy: ./deile_tasks.db) |
deile/orchestration/sqlite_task_manager.py |
| Telemetria de uso/custo | ~/.deile/db/usage.db |
deile/storage/usage_repository.py (inclui o custo da frota CLI empurrado centralmente — issue #638) |
| Memória episódica | episodes.db |
deile/memory/episodic_memory.py (aiosqlite) |
| Cron genérico | data/cron.db |
deile/cron/store.py (CronStore) |
| Ledger de custo durável (claude-worker) | ~/.claude/cost-ledger.jsonl |
claude-worker (JSONL append-only, dedup por session_id) |
| Ledger de custo durável (frota CLI) | <root>/.cost-ledger.jsonl por PVC |
cli_worker_server (fallback local; dedup por task_id) |
.deile/db/tasks.db ── task_lists ──1:N── tasks (legacy: ./deile_tasks.db)
~/.deile/db/usage.db ── usage_records (tokens, custo USD, provider/model)
episodes.db ── episódios da sessão
data/cron.db ── cron entries (CronStore + CronRunner)
~/.claude/cost-ledger.jsonl ── custo por sessão claude -p (JSONL; colhido antes de podar o transcript)
💰 Ledger de custo (issue #445): os transcripts do
claude -pacoplam continuidade--resume(volumoso, efêmero) e auditoria de custo (minúsculo, permanente). No cleanup, o custo de cada sessão órfã é colhido para o ledger durável antes de o transcript ser podado — custo histórico permanente em escala de KB, transcripts podam livremente. A ferramentainfra/k8s/session_tokens_audit.pylê o ledger (sessões podadas) + o JSONL vivo (recentes) com custo idêntico (mesma tabela de preços emjsonl_cost.py).💰 Custo central da frota CLI (issue #638): o custo dos workers da frota multi-CLI não depende mais só do ledger por-PVC (que sumia no scale-to-zero ou
force-delete). O pipeline (componente longevo) lê o blocousageestruturado da resposta do/v1/dispatche grava 1 registro por modelo noUsageRepositorycentral viafleet_cost_recorder(caminhowaitdireto, fire-and-forget capturado no reconcile via resume-info, dedup portask_id). Preço pela fonte únicajsonl_cost.fleet_cost_of_model; escrita best-effort — falha nunca derruba o dispatch. A tela[T]okensdo painel lê o store central como fonte primária.
Arquitetura hexagonal por camadas, com registries para artefatos extensíveis (tools, commands, parsers, personas, skills) — adicionar um artefato não exige tocar no núcleo (Open/Closed). I/O é async-first.
| Camada | Pacote | Responsabilidade |
|---|---|---|
| 🧩 Núcleo | deile/core/ |
Lógica central, integração com modelos, tool-loop |
| 🤖 Modelos LLM | deile/core/models/ |
Provedores, roteamento, streaming, reasoning |
| 📨 Eventos | deile/events/ |
Event bus assíncrono e handlers |
| 🛠️ Ferramentas | deile/tools/ |
Registry e implementação de tools (+ mensageria) |
| 📜 Comandos | deile/commands/ |
Slash commands e despacho |
| 🧱 Parsers | deile/parsers/ |
Parsing de entrada (arquivos, diffs, refs, comandos) |
| 🎭 Personas | deile/personas/ |
Instruções MD/YAML e manager |
| 🧩 Skills | deile/skills/ |
Discovery, registry, hot-reload |
| 🧠 Memória | deile/memory/ |
Quatro camadas |
| 🔒 Segurança | deile/security/ |
Permissões, audit, secrets scanner |
| 💾 Armazenamento | deile/storage/ |
Logger, usage/custo, budget guard, embeddings |
| 🎯 Orquestração | deile/orchestration/ |
Planos, workflows, tarefas, aprovações, pipeline, forge |
| 🩺 Runtime | deile/runtime/ |
State file por processo, status server, registry de frota |
| 🔭 Observabilidade | deile/observability/ |
Tracer/métricas OTLP, adapter de dispatch |
| ⏰ Cron | deile/cron/ |
CronStore (SQLite) + CronRunner |
| 🖥️ UI | deile/ui/ |
Renderização, streaming, painel, sub-agent panel |
| 🧬 Evolução | deile/evolution/ |
Auto-learning experimental |
| 🔌 Plugins | deile/plugins/ |
Plugin manager, hot-reload (sem sandbox — ver Limitações) |
| ⚙️ Infra | deile/infrastructure/ |
Adapters externos (SDKs, drivers) |
| 🛠️ Configuração | deile/config/ |
Settings singleton, YAML, profiles |
| 🔗 Integrações | deile/integrations/ |
Cliente HTTP do control-plane (flecha reversa agente → bot) |
| 🪵 Log mgmt | deile/log_mgmt/ |
Análise, rotação e dispatch de logs |
| 🧰 Preferências | deile/preferences/ |
Backing store das tools remember/list/forget_preference |
Fluxo de uma mensagem do usuário:
_DeileCLI(deile/cli.py) lê a entrada e encaminha aoDeileAgent—deile.pyé só o launcher que prepara o venv e delega adeile.cli.main().- Parsers extraem menções a arquivos/comandos.
- Slash command → despacha via
CommandRegistry; senão segue para o modelo. ModelRouter/TierRouterescolhe provider/modelo conforme tier e estratégia.- O provider emite
UnifiedStreamEvent(TEXT_DELTA,TOOL_USE_START/TOOL_USE_END, …). - Num evento de tool use, o
ToolLoopExecutorexecuta a tool e devolve o resultado à conversa (atémax_tool_iterations, default 100, ajustável viaDEILE_MAX_TOOL_ITERATIONS). - O
StreamingRendereracompanha eventos e atualiza o terminal ao vivo (rich.live.Live). - O
EventBuspublica eventos (telemetria, persona, tool).
A referência canônica é o .env.example (~540 linhas, seções comentadas): chaves de LLM, forges, bot, workers, pipeline (dispatch/model/reasoning por etapa), subagents, cron, OpenTelemetry, status server, etc. A leitura de config deve passar por get_settings() (deile/config/settings.py) — é um princípio arquitetural; alguns módulos do pipeline ainda leem os.environ direto (gap conhecido).
🧩 Injeção de env por worker da frota: a convenção
DEILE_CLI_<KIND>_ENV_<VARNAME>=<valor>no.envinjeta<VARNAME>no Deployment do worker<kind>ao renderizar o manifest. Sensíveis (terminam em_API_KEY/_TOKENou casamauth_env_keys) viramsecretKeyRefno Secretcli-worker-keys; não-sensíveis (ex.:OPENAI_BASE_URL,OPENAI_MODEL) entram como literal. É assim que se aponta um worker OpenAI-compat (qwen) para Dashscope/OpenRouter/OpenAI. OAuth opt-in por worker viaDEILE_<KIND>_AUTH=oauth.
~/.deile/settings.json é resolvido em três camadas com precedência project > user > profile: profile (preset) → user (~/.deile/settings.json) → project (<cwd>/.deile/settings.json, com opt-in via trust.project_layer_dirs). Ajuste por /settings set <chave> <valor> no CLI.
deile/config/(código + YAML):model_providers.yaml,intent_patterns.yaml,persona_config.yaml,commands.yaml,skills.yaml,system_config.yaml,api_config.yaml+profiles/.config/(raiz, runtime):settings.json,deilebot.yaml,pipeline_schedule_*.yaml,persona_config.yaml.
Há dois diretórios
config/(raiz edeile/). Não confundir.
- Python ≥ 3.9 · Linux/macOS (Windows experimental) · entrada
python3 deile.py.
- 🤖 LLM SDKs:
anthropic,openai,google-genai - 🖥️ UI/CLI:
rich,prompt_toolkit,colorama,Pygments - ⚡ Async I/O:
aiofiles,aiosqlite - ✅ Validação/config:
pydantic,pydantic-settings,PyYAML,python-dotenv - 🌐 Rede/sistema:
requests,httplib2,psutil,chardet,GitPython,tenacity - 📚 Outras:
numpy,pathspec,watchdog
| Extra | Para quê |
|---|---|
bot |
deilebot (git URL — repo separado elimarcavalli/deilebot) |
otel |
OpenTelemetry (api/sdk/exporter OTLP gRPC) |
ui |
textual |
scheduler · webhook · test |
APScheduler · FastAPI/uvicorn · pytest & cia. |
Testes (pytest, pytest-asyncio, pytest-mock, pytest-cov, pytest-xdist, pytest-benchmark), qualidade (coverage, isort, radon, black) e segurança (safety, bandit). (o pytest-timeout usado pelo pytest.ini vem do extra [test] do pyproject.toml.)
Configuração em pytest.ini:
testpaths = deile/tests; coletatest_*.pye*_test.py.asyncio_mode = auto(testes async dispensam@pytest.mark.asyncio).--strict-markers+--strict-config— markers novos precisam ser registrados antes de usar.- Timeout de 300 s por teste (
pytest-timeout, modo thread). - Markers registrados:
unit,integration,security,orchestration,bash,ui,slow,perf,e2e,e2e_discord_live,e2e_telegram_live,e2e_whatsapp_live,e2e_meta_live,manual,llm.
python3 -m pytest deile/tests/ -q # suíte completa (resumo)
python3 -m pytest deile/tests/path/test_x.py -v # um arquivoℹ️ O
deile/tests/mistura pytest tests (test_*.py, coletados) e scripts standalone (*_test.py,smoke_test_*.py) rodados manualmente. Testes que consomem token real ficam emdeile/tests/might/(opt-in, fora da suíte padrão). Opytest.ininão tem--cov-fail-under— o gate de cobertura é aplicado só no CI.
O CI virou gate real (hardening em 3 etapas) — todas as Actions são SHA-pinadas e cada job tem permissions: contents: read:
Etapa 1/3 — segurança & supply-chain (#732):
test— roda a suíte realdeile/tests/paralela (pytest-xdist -n auto) com--cov-fail-under=85(cobertura medida: 87%). Antes apontava paratests/(inexistente) e mascarava o exit code — CI verde era teatro (corrigido em #724).secret-scan—gitleaks(full-history) com allowlist de FPs em.gitleaks.toml(restrita aos arquivos de teste que usam segredos fake).security-scan—bandit -lll(só HIGH, zero achados legados) +pip-auditsem mascarar.
Etapa 2/3 — build, artefato & smoke (#733):
functional-tests(advisory → gating) — builda o wheel, instala em venv limpo e validadeile --version/--help(exit 0) + import dos registries core.build-and-package(advisory → gating) — wheel + sdist,twine check, smoke de install-from-wheel, todos os extras resolvidos offline ([test],[otel],[scheduler],[webhook],[ui]) em venvs isolados, Docker build da imagemdeile-stack:local(buildx + cache GHA) e smoke de import dos módulos críticos dos pods.performance-tests(que coletava 0 benchmarks — teatro) foi removido.deployment-readypassa a exigirsecret-scan,security-scan,functional-tests,build-and-package,code-qualityedocumentation.
Etapa 3/3 — qualidade de código (#736):
code-quality(advisory → gating) — dois gates sem reformatação (formatação/mypy ficam para a issue #735 de pós-reformat):interrogate deile/ --fail-under=39(cobertura de docstrings ≥ 39%; baseline 39,9% em 2026-06-16; ratchet: só pode aumentar) +radon cc deile/ -acom falha se a complexidade ciclomática média ≥ 10,0 (nota B→C; baseline A/3,24 em 2026-06-16).mypycorre em modo advisory (continue-on-error: true) até o gate real no #735.documentation(sempre presente) —scripts/validate_doc_consistency.pyverifica invariantes doc↔código (--cov-fail-undernoci.ymle não nopytest.ini, cross-refs dedocs/system_design/para arquivos existentes, presença do gate de testes);pymarkdowncorre em modo advisory (~40 violações legadas, gate real após limpeza sistemática).
| Sintoma | Causa provável / ação |
|---|---|
bootstrap_providers não acha providers |
Nenhuma API key definida — edite o .env |
Erro --strict-markers no pytest |
Marker novo não registrado — registre em pytest.ini |
| Tool "não encontrada" | Garanta que está em DEFAULT_TOOL_PACKAGES ou registrada via register_tool |
| Mudança de código no cluster sem efeito | /app é baked — rode deploy.py k8s build --restart |
Pod em erro de auth / WORKER_AUTH_EXPIRED |
Token do claude-worker expirou/ausente — re-rode deploy.py k8s claude-setup-token (token de ~1 ano; claude-renew só se ainda em OAuth legado) |
| Erro de banco durante uma tarefa | O agente para e reporta — scripts SQL -> humano |
- 🔁 Tool-loop único e provider-agnóstico — elimina duplicação por provider.
- 🌐 Fallback automático entre 4 provedores com circuit breaker por provider.
- 🧭 Forge-agnóstico — o mesmo pipeline opera GitHub e GitLab idênticos.
- 🤖 Loop autônomo DEILE-a-DEILE OU DEILE-a-Claude — escolha o worker por etapa.
- 🧠 Portão de refinamento — critica o escopo da issue antes de escrever código.
- 💵 Telemetria de custo persistente + ledger durável que sobrevive à poda de transcripts.
- 🔭 Observabilidade enterprise (OTLP) + runtime state por processo + painel TUI.
- 🎭 Personas e skills MD-driven — editáveis sem tocar no Python; hot-reload.
- 🔒 Auditoria tipada + scanner de segredos + aprovação por risco nativos.
- 🐳 Hardening de container — non-root, drop ALL caps, RO rootfs, secrets como arquivos, NetworkPolicy default-deny.
- O agente interativo não expõe servidor HTTP público — é puro CLI. Os endpoints HTTP existem só como control-plane interno do cluster (workers/pipeline-status, com Bearer).
- IDs de modelo no YAML são literais — precisam ser válidos no SDK do provider.
- Tool-loop tem teto (
max_tool_iterations, default 100, configurável). - Módulo de evolução é experimental.
- Plugins não têm sandbox real — carregue apenas plugins auditados.
/sandboxé um toggle informativo — não fornece isolamento (use a stack K8s para isolamento de verdade).- Multi-namespace é pleno para os 6 deployments core, parcial para manifests auxiliares (alguns ainda fixam
namespace: deile). - A whitelist de egress do
claude-workeré controle de aplicação (wrapper.py), não de rede (L3/L4) — gap documentado; FU prioritária é um sidecar de proxy de credenciais.
Contribuições são bem-vindas — corrija typos, crie tools, comandos, parsers, personas, skills ou providers.
| # | Etapa | Comandos gh principais |
|---|---|---|
| 1 | Explorar issues | gh issue list · gh issue view <id> |
| 2 | Criar issue (siga o template apropriado) | gh issue create --title "..." --body "..." |
| 3 | Branch vinculada | gh issue develop <id> --checkout |
| 4 | Implementar + testar | python3 -m pytest deile/tests/ -q · ruff check deile/ · isort --check-only deile/ |
| 5 | Commitar (Conventional Commits) | git add -p · git commit -m "feat(scope): ..." |
| 6 | Abrir PR | gh pr create --fill |
| 7 | Revisar / merge | gh pr review <id> --approve · gh pr merge <id> --squash |
⚠️ Edições de label devem usar o endpoint REST (gh api .../labels), nãogh issue edit --add-label— este último dispara uma query GraphQL que exige o escoporead:org(que o token do pipeline não tem).
| O quê | Onde | Observação |
|---|---|---|
| 🛠️ Tool | deile/tools/<nome>.py |
Adicione ao DEFAULT_TOOL_PACKAGES ou registre via register_tool |
| ⌨️ Slash command | deile/commands/builtin/<nome>.py |
Registre no CommandRegistry; ganha flag CLI automática |
| 🗂️ Parser | deile/parsers/<nome>.py |
Siga o contrato base + priority |
| 🎭 Persona | personas/instructions/ + personas/library/ |
MD/YAML, sem Python |
| 🧩 Skill | ~/.deile/skills/, .deile/skills/ ou deile/skills/library/ |
MD com frontmatter; hot-reload |
| 🧠 Provider | deile/core/models/ |
Registre em bootstrap.py + YAML dos modelos |
| ✔️ | Item |
|---|---|
| [ ] | Suíte completa verde (python3 -m pytest deile/tests/ -q) |
| [ ] | ruff check deile/ e isort --check-only deile/ sem pendências |
| [ ] | Commits no padrão Conventional Commits |
| [ ] | README/docs atualizados se necessário |
| [ ] | Nenhum arquivo sensível ou residual commitado |
Para detalhes de arquitetura, abra a base de conhecimento em docs/system_design/00-VISAO-GERAL.md (índice dos 14 pilares + registro de decisões).
Projeto licenciado sob MIT License.
| Nome | GitHub | Site | |
|---|---|---|---|
| Elimar Cavalli | @elimarcavalli | elimar.dev | elimar.dev@gmail.com |
| DEILE-One | @DEILE-One | elimarcavalli/deile | deile-one@elimar.dev |
DEILE — python3 deile.py
