Arquitectura del núcleo de BeeverForge

Ejecución y durabilidad

El motor es Temporal. Cada job es un workflow, cada efecto secundario es una actividad y cada espera, desde una consulta a la CI hasta una aprobación de producción, es un temporizador o una condición durable. Por eso, un reinicio del worker no pierde el job: sus esperas continúan donde estaban, y un job cuyo worker se pierde durante la pasada del agente queda en espera para que una persona lo reanude u omita, porque esa pasada no se reintenta.

Workflows, actividades y la cola de tareas#

Un único proceso worker registra todos los workflows y todas las actividades en una sola cola de tareas, beeverforge-jobs. Los workflows principales son:

JobWorkflow
Uno por ticket. El dimensionamiento, la ejecución en la caja, la vigilancia hasta el merge, la promoción y cada espera ocurren dentro de él.
PollWorkflow
Una exploración del tablero por ciclo, que inicia jobs en los cupos libres.
DeployWatchWorkflow
Un hijo independiente que observa el despliegue después de un merge.
PreviewWorkflow
Una vista previa en ejecución de un cambio sin merge, cuando hay un entorno de vista previa configurado.
ConversationWorkflow
Una conversación con la fábrica, admitida y avanzada mediante señales.

Otros workflows cubren la vigilancia del tech-lead, la actualización del conocimiento, el rol de producto y los barridos que se listan abajo. El trabajo pesado, como ejecutar el job en su caja, es la actividad run_job.

De un ciclo de sondeo a una versiónLa programación inicia el workflow de sondeo, cuyas actividades leen los tableros, cuentan los cupos libres e inician un workflow de job por cupo libre. El workflow del job ejecuta el dimensionamiento y el job como actividades, el job se ejecuta en su caja, y luego el workflow espera con temporizadores durables hasta el merge, y con señales las respuestas de una persona.CajaActividadesJobWorkflowPollWorkflowLo inicia la programacióncada 3 minutosloop[Hasta el merge, como máximo 14días]opt[Promoción habilitada]Una personascan_projects, available_slotsscan_todocomprueba la certificación de la cajastart_jobsInicio, uno por cupo librepreflight_checkdimensionamientorun_jobheartbeat cada 30 s aproximadamentesetup:, pasada del agente,controles validate:, revisiónEl pull request está abiertoTemporizador durable: 2 minutos, luego 15check_pr_status, checks de la CI,repair_ci si está en rojohuman_merge_gatesellado, desde el panelpromote_stagingapprove_prodsellado, en 3 días como máximoverify_gate_seal,luego release_prod
Un job desde el sondeo hasta una versión. Las llamadas a Actividades se ejecutan en el worker; las respuestas de una persona llegan como señales. Cada espera es un temporizador o una condición durable, así que ninguna ocupa un worker.

Programaciones#

El trabajo recurrente es una programación de Temporal, que el worker concilia al arrancar.

ProgramaciónIntervaloPropósito
beeverforge-poller3 minutosExplorar los tableros; un ciclo que se solaparía se omite.
beeverforge-techlead-watch-*1 horaLa ronda del tech-lead sobre el trabajo en espera.
beeverforge-card-questions-*1 horaLeer las respuestas a las preguntas hechas en las tarjetas.
beeverforge-okf-refresh-*6 horasActualizar el mapa de conocimiento respecto a la rama base.
beeverforge-product-sweep-*1 semanaEl barrido del rol de producto en busca de trabajo que no puede terminar.
beeverforge-preview-reaper10 minutosTerminar las vistas previas cuyo pull request ya recibió el merge.

El motor conserva el historial de los workflows durante 30 días de forma predeterminada (BEEVERFORGE_ENGINE_RETENTION_DAYS).

Señales y temporizadores durables#

La respuesta de una persona llega a un job en curso como una señal: approve_prod, not_yet, human_merge_gate, act_on_impediment y advise_decision. Las esperas son workflow.sleep y workflow.wait_condition con un tiempo límite, de modo que una espera de minutos o días no ocupa ningún worker y sobrevive a un reinicio. Por ejemplo, la consulta a la CI (2 minutos, luego 15), la consulta del despliegue (1 minuto), la aprobación de producción (3 días), la vigilancia del merge (14 días) y un impedimento en espera (3 días).

Efectos secundarios idempotentes#

Una actividad puede volver a ejecutarse tras un fallo, así que cada escritura externa está hecha para poder repetirse sin riesgo o no se reintenta:

  • Los pull requests son buscar y luego actualizar: se reutiliza el pull request abierto de la rama.
  • Las etiquetas de versión son buscar o crear.
  • Las transiciones y los comentarios de las tarjetas llevan el id del evento de la actividad que los causó; la puerta de la tarjeta devuelve la transición ya registrada para ese id en lugar de escribirla de nuevo.
  • Los merges se vuelven a leer en la plataforma Git después de la llamada de merge, en lugar de confiar en el resultado de la llamada.
  • El propio job solo se reintenta cuando su tipo de caja declara que un reintento se vuelve a conectar al job en curso. Las cajas container y worktree no lo declaran: ejecutan su actividad de job una sola vez, porque un reintento sería una segunda pasada del agente que repetiría cada comentario. La descripción fargate sí lo declara; su ejecutor lo proporciona un complemento.

Heartbeats#

Las actividades largas emiten heartbeats. Las actividades de job se ejecutan con un tiempo límite de heartbeat de 120 segundos y laten aproximadamente cada 30 segundos; el trabajo que continúa después del último latido está acotado para caber en esa ventana. Así, un worker que muere a mitad de una actividad se detecta dentro del tiempo límite y no al final de la duración completa de la actividad.

Seguridad en la reejecución#

Temporal reproduce el historial de un workflow contra el código actual, así que un cambio en la lógica del workflow no debe cambiar los comandos que un workflow en curso ya registró.

  • Cada cambio de ese tipo está protegido por workflow.patched() con un id que nombra el comportamiento, por ejemplo signed-gates o a-self-merge-is-read-back. Los historiales antiguos toman el camino antiguo; las ejecuciones nuevas toman el nuevo.
  • Las pruebas de Replayer ejecutan un workflow contra un servidor Temporal efímero, obtienen el historial registrado y lo reejecutan con Replayer contra el código actual. Algunas ejecutan primero una copia del workflow anterior al parche. Los historiales se registran dentro de las pruebas; ninguno está guardado como archivo en el repositorio.
  • El código que debe quedar fuera del cuerpo del workflow se mantiene en actividades. La comprobación de la certificación de la caja al recoger trabajo está en la actividad de exploración del tablero precisamente por eso.

Un cliente del motor por proceso#

Un proceso mantiene un cliente del motor. runtime/temporal/connection.py conserva un único cliente identificado por la configuración de la conexión; los primeros llamadores concurrentes comparten un único intento de conexión, y la reconexión espera como máximo 60 segundos. Pruebas de guarda comprueban que las primeras llamadas concurrentes, y las aprobaciones repetidas, abren un solo cliente.

Estructuras acotadas#

El worker y el panel son procesos de larga vida, así que las cachés en memoria están acotadas por construcción.

  • util/bounded.py proporciona BoundedDict: seguro entre hilos, con un tamaño máximo obligatorio, que expulsa primero la entrada más antigua y cuenta lo que expulsó. Deliberadamente no es una subclase de dict, para que nada pueda hacerlo crecer saltándose su límite.
  • util/single_flight.py permite que los lectores que llegan a la vez compartan una lectura. Cada uno queda protegido de la cancelación de otro llamador, hay como máximo una lectura en curso, y un fallo se comparte con quienes esperan en ese momento y no se guarda.
  • Una prueba estructural, tests/test_no_unbounded_growth.py, exige que cada lugar que reserva o memoiza datos en un proceso de larga vida declare su límite.

Diario, métricas y observabilidad#

  • El diario del job registra eventos tipados (state, agent_action, validation, review, pr, note, warning, error). El destino predeterminado es un archivo, <issue>-events.jsonl en el directorio de registros del proyecto; BEEVERFORGE_EVENT_SINK selecciona otro.
  • Las métricas registran el costo y el esfuerzo por invocación del agente y por job. Los destinos incluidos son null, sqlite y memory. Sin configuración el destino es null y no se guarda nada, salvo que BEEVERFORGE_METRICS_TABLE nombre una tabla, lo que selecciona dynamodb, un tipo que proporciona un complemento. El panel las sirve en GET /api/metrics.
  • El estado de salud tiene sus propias rutas, y la interfaz web de Temporal muestra el historial de cada workflow.
  • El núcleo no tiene exportador de Prometheus ni de OpenTelemetry.

API del panel#

El panel es una aplicación FastAPI. Las lecturas tipadas se sirven bajo /api/v1/, y sus rutas sin versión siguen existiendo, marcadas como obsoletas. La descripción OpenAPI está versionada en docs/reference/openapi.json; una prueba comprueba que el archivo versionado es igual al que genera la aplicación e indica el comando que lo regenera, python -m beeverforge.api.openapi.

Fuentes#