Arquitetura do núcleo do BeeverForge

Execução e durabilidade

O motor é o Temporal. Cada job é um workflow, cada efeito colateral é uma atividade e cada espera, de uma consulta à CI a uma aprovação de produção, é um temporizador ou uma condição durável. Por isso, reiniciar um worker não perde o job: suas esperas continuam de onde estavam, e um job cujo worker se perde durante a passada do agente fica em espera para que uma pessoa o retome ou pule, porque essa passada não é repetida.

Workflows, atividades e a fila de tarefas#

Um único processo worker registra todos os workflows e todas as atividades em uma só fila de tarefas, beeverforge-jobs. Os principais workflows são:

JobWorkflow
Um por ticket. O dimensionamento, a execução na caixa, o acompanhamento até o merge, a promoção e cada espera acontecem dentro dele.
PollWorkflow
Uma varredura do quadro por ciclo, que inicia jobs nas vagas livres.
DeployWatchWorkflow
Um filho independente que observa a implantação depois de um merge.
PreviewWorkflow
Uma prévia em execução de uma mudança sem merge, quando há um ambiente de prévia configurado.
ConversationWorkflow
Uma conversa com a fábrica, admitida e conduzida por sinais.

Outros workflows cobrem a vigilância do tech-lead, a atualização do conhecimento, o papel de produto e as varreduras listadas abaixo. O trabalho pesado, como executar o job na sua caixa, é a atividade run_job.

De um ciclo de consulta a uma versãoO agendamento inicia o workflow de consulta, cujas atividades leem os quadros, contam as vagas livres e iniciam um workflow de job por vaga livre. O workflow do job executa o dimensionamento e o job como atividades, o job roda na sua caixa, e depois o workflow espera com temporizadores duráveis até o merge, e com sinais as respostas de uma pessoa.CaixaAtividadesJobWorkflowPollWorkflowIniciado pelo agendamentoa cada 3 minutosloop[Até o merge, no máximo 14 dias]opt[Promoção habilitada]Uma pessoascan_projects, available_slotsscan_todoverifica a prova da caixastart_jobsInício, um por vaga livrepreflight_checkdimensionamentorun_jobheartbeat a cada 30 s, aproximadamentesetup:, passada do agente,controles validate:, revisãoO pull request está abertoTemporizador durável: 2 minutos, depois 15check_pr_status, checks da CI,repair_ci quando vermelhohuman_merge_gateselado, a partir do painelpromote_stagingapprove_prodselado, em até 3 diasverify_gate_seal,depois release_prod
Um job da consulta até uma versão. As chamadas a Atividades rodam no worker; as respostas de uma pessoa chegam como sinais. Cada espera é um temporizador ou uma condição durável, então nenhuma ocupa um worker.

Agendamentos#

O trabalho recorrente é um agendamento do Temporal, que o worker reconcilia ao iniciar.

AgendamentoIntervaloFinalidade
beeverforge-poller3 minutosVarrer os quadros; um ciclo que se sobreporia é ignorado.
beeverforge-techlead-watch-*1 horaA ronda do tech-lead sobre o trabalho em espera.
beeverforge-card-questions-*1 horaLer as respostas às perguntas feitas nos cartões.
beeverforge-okf-refresh-*6 horasAtualizar o mapa de conhecimento em relação ao branch base.
beeverforge-product-sweep-*1 semanaA varredura do papel de produto em busca de trabalho que não pode terminar.
beeverforge-preview-reaper10 minutosEncerrar as prévias cujo pull request já recebeu merge.

O motor guarda o histórico dos workflows por 30 dias por padrão (BEEVERFORGE_ENGINE_RETENTION_DAYS).

Sinais e temporizadores duráveis#

A resposta de uma pessoa chega a um job em andamento como um sinal: approve_prod, not_yet, human_merge_gate, act_on_impediment e advise_decision. As esperas são workflow.sleep e workflow.wait_condition com tempo limite, de modo que uma espera de minutos ou dias não ocupa nenhum worker e sobrevive a uma reinicialização. Exemplos são a consulta à CI (2 minutos, depois 15), a consulta da implantação (1 minuto), a aprovação de produção (3 dias), o acompanhamento do merge (14 dias) e um impedimento em espera (3 dias).

Efeitos colaterais idempotentes#

Uma atividade pode ser executada de novo após uma falha, então cada escrita externa é feita para ser repetida com segurança ou não é repetida:

  • Os pull requests são buscar e depois atualizar: o pull request aberto do branch é reutilizado.
  • As tags de versão são buscar ou criar.
  • As transições e os comentários dos cartões levam o id do evento da atividade que os causou; a porta do cartão devolve a transição já registrada para esse id em vez de escrevê-la de novo.
  • Os merges são lidos de novo na plataforma Git depois da chamada de merge, em vez de confiar no resultado da chamada.
  • O próprio job só é repetido quando seu tipo de caixa declara que uma nova tentativa se reconecta ao job em andamento. As caixas container e worktree não declaram isso: executam sua atividade de job uma única vez, porque uma nova tentativa seria uma segunda passada do agente que repetiria cada comentário. A descrição fargate declara isso; seu executor é fornecido por um complemento.

Heartbeats#

As atividades longas emitem heartbeats. As atividades de job rodam com tempo limite de heartbeat de 120 segundos e batem a cada 30 segundos aproximadamente; o trabalho que continua depois da última batida é limitado para caber nessa janela. Assim, um worker que morre no meio de uma atividade é detectado dentro do tempo limite, e não ao fim da duração completa da atividade.

Segurança na reexecução#

O Temporal reexecuta o histórico de um workflow contra o código atual, então uma mudança na lógica do workflow não pode alterar os comandos que um workflow em andamento já registrou.

  • Cada mudança desse tipo é protegida por workflow.patched() com um id que nomeia o comportamento, por exemplo signed-gates ou a-self-merge-is-read-back. Os históricos antigos seguem o caminho antigo; as novas execuções seguem o novo.
  • Os testes de Replayer executam um workflow contra um servidor Temporal efêmero, obtêm o histórico registrado e o reexecutam com Replayer contra o código atual. Alguns executam antes uma cópia do workflow de antes do patch. Os históricos são registrados dentro dos testes; nenhum é versionado como arquivo.
  • O código que precisa ficar fora do corpo do workflow é mantido em atividades. A verificação da prova da caixa no recolhimento fica na atividade de varredura do quadro exatamente por isso.

Um cliente do motor por processo#

Um processo mantém um cliente do motor. runtime/temporal/connection.py guarda um único cliente identificado pelas configurações da conexão; os primeiros chamadores concorrentes compartilham uma única tentativa de conexão, e a reconexão espera no máximo 60 segundos. Testes de guarda verificam que as primeiras chamadas concorrentes, e as aprovações repetidas, abrem um único cliente.

Estruturas limitadas#

O worker e o painel são processos de longa duração, então os caches em memória são limitados por construção.

  • util/bounded.py fornece BoundedDict: seguro entre threads, com tamanho máximo obrigatório, que remove primeiro a entrada mais antiga e conta o que removeu. De propósito, não é uma subclasse de dict, para que nada possa fazê-lo crescer contornando seu limite.
  • util/single_flight.py permite que leitores que chegam juntos compartilhem uma leitura. Cada um fica protegido do cancelamento de outro chamador, há no máximo uma leitura em andamento, e uma falha é compartilhada com quem está esperando naquele momento e não é guardada.
  • Um teste estrutural, tests/test_no_unbounded_growth.py, exige que cada ponto que aloca dados ou faz memoização em um processo de longa duração declare seu limite.

Diário, métricas e observabilidade#

  • O diário do job registra eventos tipados (state, agent_action, validation, review, pr, note, warning, error). O destino padrão é um arquivo, <issue>-events.jsonl no diretório de logs do projeto; BEEVERFORGE_EVENT_SINK seleciona outro.
  • As métricas registram custo e esforço por invocação do agente e por job. Os destinos incluídos são null, sqlite e memory. Sem configuração, o destino é null e nada é guardado, a menos que BEEVERFORGE_METRICS_TABLE nomeie uma tabela, o que seleciona dynamodb, um tipo fornecido por um complemento. O painel as serve em GET /api/metrics.
  • A saúde tem rotas próprias, e a interface web do Temporal mostra o histórico de cada workflow.
  • O núcleo não tem exportador de Prometheus nem de OpenTelemetry.

API do painel#

O painel é uma aplicação FastAPI. As leituras tipadas são servidas em /api/v1/, e seus caminhos sem versão continuam existindo, marcados como obsoletos. A descrição OpenAPI está versionada em docs/reference/openapi.json; um teste verifica que o arquivo versionado é igual ao que a aplicação gera e mostra o comando que o regenera, python -m beeverforge.api.openapi.

Fontes#