Voltar as noticias
Por que rejeitei um Event Bus para minha frota de agentes solo: Estado é verdade, eventos são rumores
MCP ProtocolAltaEN

Por que rejeitei um Event Bus para minha frota de agentes solo: Estado é verdade, eventos são rumores

Dev.to - MCP·12 de julho de 2026

Publicado originalmente em notas hexisteme.

Eu gerencio uma frota pessoal em uma máquina: um punhado de pequenos CLIs de agente, uma pilha de jobs cron e LaunchAgents, e vários servidores MCP, alguns deles ferramentas de terceiros que eu não escrevi e não posso modificar. Em algum momento, eu queria uma única caixa de entrada que pudesse responder a uma pergunta — o que precisa da minha atenção agora? Novas saídas que eu ainda não li. Decisões que apenas um humano pode fechar. Dependências que quebraram da noite para o dia sem avisar ninguém.

A arquitetura óbvia para isso é um barramento de eventos. Eu esbocei, então rejeitei de propósito e construí um design baseado em pull. A razão se generaliza além da minha configuração particular, então aqui está.

Para uma frota pequena e de alta rotatividade que você não controla totalmente, pull (escanear o estado em disco) supera push (um barramento de eventos). Um barramento de eventos precisa que cada produtor — incluindo ferramentas de terceiros que você não pode modificar — emita; qualquer coisa que não emita é silenciosamente invisível. Um poller que lê o estado em disco em vez disso — mtimes de arquivos, verificações de saúde, vivacidade de processos — é auto-curativo: reflete a realidade mesmo para componentes que nunca relatam nada, e uma nova ferramenta aparece no momento em que escreve um arquivo, sem instrumentação nenhuma.

A arquitetura que quase construí

A versão do barramento de eventos parece limpa em um quadro branco. Cada componente emite eventos — job.finished, output.created, decision.pending — em um log de apenas anexação. A caixa de entrada é apenas um leitor desse log. Fechar um item escreve um evento de fechamento que avança o que vem a seguir. É o padrão de "única fonte de verdade" do livro didático, e é a primeira coisa que a maioria das pessoas busca.

Eu o matei por quatro razões.

Razão 1: o imposto de instrumentação é um assassino de projetos

Push significa que cada produtor tem que emitir. Em uma frota que cresce na maioria das semanas, isso é um imposto constante sobre cada novo agente, cada nova linha cron, e — a que realmente matou o design — cada servidor MCP de terceiros que eu não escrevi. Você não pode adicionar uma chamada emit() a um servidor que alguém mais construiu. Então, no momento em que adiciono um componente e esqueço de instrumentá-lo, ou simplesmente não posso, ele se torna invisível na caixa de entrada.

Essa é a armadilha: os componentes que você menos controla — ferramentas de terceiros, coisas que falham cedo — são exatamente aqueles que um barramento de eventos não consegue ver. Push otimiza para o caso fácil (código que você possui) e falha silenciosamente no caso difícil (código que você não possui). Uma camada de observabilidade cujos pontos cegos crescem com o sistema é pior do que nenhuma camada de observabilidade, porque ainda parece autoritativa enquanto mente silenciosamente.

Razão 2: o estado é verdade, eventos são rumores

Um evento é uma afirmação que depende do reclamante sobreviver tempo suficiente para fazê-la. Se um agente falha antes de emitir done, um monitor baseado em eventos não mostra nada — a falha é invisível. O estado é a evidência deixada para trás, independentemente de alguém ter se lembrado de relatá-la: um arquivo de saída obsoleto, um log que parou de crescer, uma verificação de saúde que falha, um processo que simplesmente não está mais lá.

# pull: derive status from evidence the component already leaves behind
status = {
    "alive":   process_is_running(job),               # ps / launchctl print
    "fresh":   newest_output_mtime(job) > expected,    # <project>/report/ glob
    "healthy": health_check(dependency),               # poll endpoint
}
# no emit() anywhere — a component that never reports is still seen

O estado é verdade, eventos são rumores. Um evento só existe se algo se lembrou de enviá-lo; o estado é a evidência deixada para trás, independentemente. Um monitor de pull lê essa evidência e reflete uma falha sem precisar da cooperação do agente — o que o torna auto-curativo, convergindo para a realidade a cada ciclo, enquanto o push é sempre tão honesto quanto seu emissor menos confiável.

Razão 3: esquizofrenia de orquestração

Um barramento de loop fechado, onde "fechar este item" emite um evento que avança o próximo passo, silenciosamente transforma a caixa de entrada em um motor de fluxo de trabalho. Eu já executo um hub de orquestração que decompõe metas em etapas. Construir um segundo dentro do monitor duplica essa responsabilidade e dobra a superfície de depuração — agora uma tarefa presa pode ser culpa do hub ou da caixa de entrada, e eu tenho que verificar ambos. Um monitor deve relatar o estado, não conduzi-lo. Manter essas duas funções em dois sistemas separados é o que mantém cada um depurável.

Razão 4: o barramento em si se torna um despejo não revisado

Um log de eventos de apenas anexação não é uma infraestrutura gratuita. Ele acumula deriva de esquema — a forma de output.created muda e leitores antigos quebram — além de eventos duplicados, eventos que ninguém nunca fecha, e crescimento ilimitado que eventualmente exige compactação. Eu estaria adicionando um banco de dados sem nenhuma das garantias de um banco de dados, e precisaria de sua própria monitorização apenas para saber se ele estava saudável. O pull não produz esse artefato: não há nada para compactar, porque não há nada armazenado além do estado que já existe em disco.

Como é o pull em vez disso

A caixa de entrada acaba sendo uma visão computada sobre o estado que já existe — sem novo armazenamento, sem chamadas emit() em nenhum lugar:

Tipo de atenção Derivado de (pull)
Nova / saída não lida glob mtime de saída por trabalho vs. um registro de timestamp de leitura
Decisão pendente fontes existentes em disco — a saída de uma varredura de atenção, uma entrada de livro-razão não decidida, uma data de avaliação expirada
Dependência quebrada falha na verificação de saúde, propagada para qualquer um que declare uma dependência dela

A prioridade é determinística, não uma suposição de modelo: BLOCKED → STALE → NEW, onde cada um é um fato duro — uma verificação de saúde falhada, um arquivo mais novo que seu timestamp de leitura, um cronograma ultrapassado. Um número de prioridade gerado por LLM seria infalsificável e se desviaria ao longo do tempo; um gatilho determinístico é reproduzível e depurável. Eu mantenho o modelo fora da classificação completamente.

A frescura é "não lida", não "velha"

Pull também corrigiu uma métrica que eu tinha errada. Meu primeiro instinto para "frescor" foi o tempo decorrido — isso foi executado há três dias, então está obsoleto. Mas a idade não é realmente o problema; saída não lida é. Um relatório que terminou há uma hora e que eu não abri ainda exige mais atenção do que um de semana passada que eu já li.

Então, a frescura é computada como uma junção: existe uma saída cujo mtime é mais novo do que a última vez que eu a abri? Clicar em um cartão registra um timestamp de leitura; itens não lidos sobem para o topo, os lidos afundam. Essa junção é barata apenas porque o design já escaneia o estado para tudo.

Contexto Triplo Up

Empresas brasileiras que utilizam múltiplos agentes e ferramentas de automação podem se beneficiar de uma arquitetura pull, que oferece maior resiliência e visibilidade sobre o estado dos sistemas. Essa abordagem pode reduzir a complexidade e melhorar a confiabilidade na gestão de processos.

Noticias relacionadas

Gostou do conteudo?

Receba toda semana as principais novidades sobre WebMCP.