13 KiB
Architecture Decision Records — Runtime de consolidação de artigos
Versão do conjunto: 1.0
Data: 23 de agosto de 2026
Escopo: somente runtime
Regra mestre: cada decisão deve atender a um requisito ou risco real de produção com a menor complexidade necessária.
Convenções
Cada ADR registra uma decisão arquitetural individual. Todas apresentam o estado aprovado da arquitetura para implementação.
ADR-001 — Separar runtime e self-healing
Status: Accepted
Contexto
O runtime precisa consolidar artigos em produção. O self-healing de prompts adiciona análise de falhas, modelos potentes, LLM-as-a-judge, promoção e rollback de prompts. Implementar ambos simultaneamente mistura objetivos e aumenta risco e código antes da validação do fluxo principal.
Decisão
Runtime e self-healing serão projetos documentais e técnicos separados.
O runtime apenas registra a telemetria necessária ao futuro projeto. Não implementa otimizador, judge, prompt candidato, promoção, canário, rollback automático de prompt ou alerta de self-healing.
Consequências
- foco integral no processamento principal;
- menor superfície operacional;
- self-healing só começa após estabilização do runtime;
- os documentos do runtime apenas referenciam a capacidade futura.
Alternativa rejeitada
Construir o ciclo completo de self-healing junto com o runtime.
ADR-002 — Iniciar o runtime após a seleção do extrator
Status: Accepted
Contexto
Um processo anterior já executa Trafilatura, Newspaper4k e Readability e determina selected_extractor.
Decisão
Cada execução recebe um único objeto de artigo já contendo selected_extractor. O runtime valida o campo e a disponibilidade do extrator, mas não calcula, recalcula ou corrige a seleção.
O wrapper de lote com articles não pertence ao contrato da execução.
Consequências
- fronteira clara entre seleção e consolidação;
- runtime menor;
- entrada inválida falha cedo;
- nenhuma divergência silenciosa com a decisão anterior.
Alternativas rejeitadas
- recalcular sempre a seleção;
- aceitar lote e selecionar internamente;
- substituir silenciosamente extrator inválido.
ADR-003 — Usar orquestração explícita em Python, sem LangChain ou LangGraph
Status: Accepted
Contexto
O fluxo combina etapas determinísticas, chamadas de LLM, estados persistidos e bifurcações entre validação, higienização, ECP, enriquecimento, fallback e saída.
LangGraph pode operar como motor standalone de workflows com estado e combinar passos determinísticos e probabilísticos. Portanto, sua rejeição não pode ser fundamentada apenas no fato de o runtime não ser um agente ou de o fluxo ser linear.
No runtime atual, porém:
- a sequência é predeterminada;
- as bifurcações são poucas, conhecidas e controladas pela aplicação;
- o modelo não escolhe livremente ferramentas nem a próxima etapa;
- não existem ciclos semânticos;
- não existe human-in-the-loop;
- não há pausa aguardando evento externo;
- cada execução processa um artigo e tem curta duração;
- SQLite continua necessário para idempotência, reconciliação, erros e escrita atômica;
- LangGraph não substituiria gateway, prompts, schemas, harness, validações, persistência, observabilidade ou testes.
LangChain também não elimina implementação relevante, pois as chamadas aos modelos são delimitadas e o gateway agnóstico já encapsula providers, modelos, parâmetros, retry técnico e fallback.
Decisão
Implementar orquestração direta em Python com máquina de estados persistida em SQLite. Não usar LangChain, LangGraph, agentes ou planner no runtime.
Langfuse permanece como observabilidade online e Promptfoo como ferramenta de eval e gate fora do runtime. Ambos são independentes de LangChain e LangGraph.
Consequências
- menor código indireto e menos dependências;
- estados, erros e retomadas explícitos;
- testes por etapa simples;
- uma única implementação de estado e retomada;
- novas bifurcações predeterminadas permanecem na orquestração direta;
- introdução de framework exige requisito concreto e redução comprovada de complexidade própria.
Critérios de reavaliação
Reavaliar LangGraph somente se o runtime passar a exigir uma ou mais capacidades que alterem materialmente o fluxo atual:
- ciclos semânticos entre etapas;
- pausa e retomada aguardando aprovação humana ou evento externo;
- roteamento dinâmico de etapas decidido por modelo;
- coordenação de agentes ou subfluxos dinâmicos;
- execução longa em que checkpoint por etapa reduza materialmente custo ou perda de trabalho;
- substituição de persistência ou orquestração própria relevante pelo framework, sem duplicação de responsabilidades.
A existência isolada de estados, chamadas de LLM ou arestas condicionais não é critério suficiente.
Alternativas rejeitadas
- LangChain para encapsular chamadas simples: rejeitado porque o gateway já fornece a abstração necessária e não há agente ou tool calling.
- LangGraph standalone como motor do workflow atual: rejeitado porque adicionaria dependência e semântica operacional sem eliminar implementação relevante.
- Agente autônomo para escolher etapas: rejeitado porque retiraria previsibilidade de um processo com sequência conhecida.
ADR-004 — Gateway agnóstico com apenas modelos baratos no runtime
Status: Accepted
Contexto
Providers e modelos podem mudar por custo, qualidade, disponibilidade ou descontinuação. Modelos potentes devem ser reservados ao self-healing futuro.
Decisão
O domínio depende de dois papéis configuráveis:
runtime_primary;runtime_fallback.
O gateway traduz esses papéis para provider, modelo, parâmetros, timeout e prompt certificado. Ambos devem ser baratos. Nenhum caminho do runtime chama modelo potente.
Consequências
- troca de modelo sem alterar regras de domínio;
- toda combinação exige Promptfoo antes de promoção;
- falha dos modelos baratos termina com fallback determinístico seguro ou falha controlada;
- não existe escalada cara por artigo.
Alternativas rejeitadas
- SDK de provider espalhado pelo domínio;
- roteador inteligente com vários modelos;
- escalada automática para Sol, Terra ou equivalente em artigos difíceis.
ADR-005 — Proibir regex e palavras-chave manuais em decisões textuais
Status: Accepted
Contexto
O corpus é multilíngue. Regex e listas de palavras produzem falsos positivos e negativos grosseiros em classificação e higienização textual.
Decisão
Nenhum módulo do pipeline textual ou assertion de conteúdo pode usar regex. Nenhuma decisão semântica pode depender de dicionário manual de palavras por idioma.
Usar parsers estruturais, bibliotecas Unicode, tokenizadores, segmentadores, NLP e LLM quando houver interpretação semântica.
O CI verifica o código Python por AST para impedir uso direto de engine de regex nos módulos relevantes.
Consequências
- menor fragilidade multilíngue;
- decisões estruturais separadas das semânticas;
- qualquer exceção futura exige nova ADR explícita;
- bibliotecas transitivas podem internamente usar seus próprios algoritmos, mas o projeto não chama APIs de regex para texto.
Alternativas rejeitadas
- regex por domínio;
- dicionários traduzidos;
- regras como título contém determinada palavra;
- comprimento textual como classificador final.
ADR-006 — LLM seleciona evidências e propõe reparos, não regenera o artigo
Status: Accepted
Contexto
O LLM precisa remover ruído e organizar o conteúdo, mas não pode reescrever, resumir ou inventar. Pequenos defeitos textuais precisam ser corrigíveis.
Decisão
O LLM de higienização retorna:
- IDs de metadados e blocos;
- IDs de links e imagens;
- operações explícitas de reparo.
Ele não retorna livremente o artigo completo.
Cada reparo referencia fragmento original, substituição, categoria e justificativa. O harness valida e aplica. Reparo inválido é descartado e o original permanece.
Consequências
- grounding verificável;
- reparos auditáveis e reversíveis;
- renderer totalmente controlado pela aplicação;
- prompt, eval e harness precisam distinguir reparo de reescrita.
Alternativas rejeitadas
- pedir ao LLM um Markdown final livre;
- proibir qualquer correção;
- aceitar texto corrigido sem diff e origem.
ADR-007 — Tornar o ECP obrigatório antes de toda saída editorial
Status: Accepted
Contexto
Markdown só deve existir para conteúdo inerente à entidade definida pelo ECP.
Decisão
Toda execução exige ECP válido.
- O ECP recebe o Markdown intermediário higienizado.
DIRECT_INHERENTeCONTEXTUAL_INHERENTprosseguem.TANGENTIALeNOT_RELATEDnão geram Markdown.
O runtime usa o schema e o classificador ECP existentes, sem duplicá-los.
Consequências
- nenhuma saída escapa do gate de relevância;
- ECP inválido encerra antes do LLM;
- o pipeline depende explicitamente do contrato versionado do ECP.
Alternativas rejeitadas
- permitir execução sem ECP;
- aplicar ECP depois da geração do arquivo final.
ADR-008 — Langfuse no runtime e Promptfoo no CI
Status: Accepted
Contexto
O runtime requer observabilidade de produção e evals de promoção, mas não deve acoplar execução online a ferramentas de teste.
Decisão
- Langfuse recebe traces, spans, generations, scores, custos e versões.
- Promptfoo executa evals, comparação de modelos baratos e gates no CI/staging.
- Prompts versionados no repositório são a fonte primária.
- Falha do Langfuse não bloqueia resultado; telemetria mínima fica pendente.
- Promptfoo não é chamado pelo runtime.
Consequências
- responsabilidades claras;
- runtime não depende da disponibilidade do sistema de eval;
- prompts usados no CI e produção são idênticos;
- dados suficientes ficam disponíveis ao futuro self-healing.
Alternativas rejeitadas
- Promptfoo online por artigo;
- Langfuse como única fonte de prompt nesta versão;
- bloquear processamento quando observabilidade estiver indisponível.
ADR-009 — Persistir estado em SQLite e saídas no filesystem
Status: Accepted
Contexto
O runtime é uma CLI, processa um artigo por execução e precisa de idempotência, retomada, escrita atômica e telemetria pendente. O volume é de até 100 artigos por hora.
Decisão
Usar:
- SQLite para estado, fingerprints, versões, erros e telemetria pendente;
- WAL e transações curtas para concorrência;
- filesystem para manifesto e Markdown;
- nomes baseados em fingerprint;
- arquivos temporários e rename atômico.
Não introduzir Postgres, fila ou object storage no runtime enquanto staging não demonstrar necessidade.
Consequências
- deployment simples;
- menor custo operacional;
- concorrência e disco precisam ser monitorados;
- migração futura depende de evidência de gargalo ou requisito novo.
Alternativas rejeitadas
- persistência apenas em memória;
- arquivos sem índice idempotente;
- Postgres antecipado;
- fila dedicada para telemetria.
ADR-010 — Produzir resultado estruturado sempre e Markdown condicionalmente
Status: Accepted
Contexto
Rejeição ECP e falhas controladas precisam de resultado legível por máquina mesmo quando não existe Markdown.
Decisão
Toda invocação devolve resultado JSON estruturado. Quando a entrada for parseável e houver fingerprint estabelecido, o resultado também é persistido como manifesto. Markdown é produzido somente quando existe texto editorial aprovado pelo ECP e enriquecimento válido.
O manifesto informa estado, ECP, versões, trace e erro.
Consequências
- saída não ambígua;
- auditoria possível sem Markdown;
- entrada que nem possa ser parseada retorna erro estruturado e log, sem exigir arquivo persistente.
Alternativas rejeitadas
- não produzir saída para descartes esperados.
ADR-011 — Definir SLOs de custo e latência a partir de staging
Status: Accepted
Contexto
Não existe baseline real de tokens, custo e duração para a cadeia final. Definir números agora seria arbitrário.
Decisão
- instrumentar custo e latência desde o primeiro teste;
- executar staging com o corpus representativo e 100 artigos por hora;
- calcular p50, p95 e p99 por etapa e total;
- medir custo por artigo recebido e por Markdown aprovado;
- aprovar limites operacionais antes do go-live;
- impedir produção enquanto esses limites não estiverem registrados.
Consequências
- SLOs baseados em evidência;
- documentação não contém números inventados;
- staging possui gate operacional obrigatório.
Alternativa rejeitada
Escolher limites de custo e latência sem dados do pipeline implementado.